DDR · Module 31
DDR vs GDDR
The graphics variant buys per-pin rate with topology and pays in replaceability and granularity. The deciding quantity is the consumer's latency tolerance, and a faster channel raises the concurrency bar rather than lowering it.
Chapter 31.3 §4 said there are exactly two ways past the pin-count wall: make the connection count scale like the area, or stop using the perimeter. This chapter is about the third thing people actually do, which is neither.
CURRICULUM-DERIVED from 26.1 §1: a cleverer interface raises the connections-per-millimetre or the rate each one carries, which multiplies the numerator by a constant — and it cannot turn 4/L into anything that does not fall.
The graphics variant is that constant factor, bought deliberately and pushed as far as a soldered point-to-point topology allows. Knowing that it is a constant factor is knowing its limit: it postpones the wall and never removes it.
And the deciding quantity is not on the memory side of the interface at all.
The deciding quantity is the consumer's latency tolerance. Once a requester can keep enough transactions in flight that the pipeline depth stops being observable, latency stops being an objective — and everything that was being spent to protect it becomes available to spend on rate instead.
So this chapter is organised around two lists: what becomes affordable to give up once latency stops mattering, and what must be added back once the per-pin rate rises far enough that the channel is no longer reliable on its own. The second list is the one that surprises people, and it is why this is not simply DDR with the clock turned up.
Axis A1 is identical for the third chapter running. Same destructive cell, same thirteen obligations. This module's recurring lesson is that comparisons between memory technologies almost never live at the cell.
1. What This Comparison Cannot Be About
Three chapters have established that axis A1 does not differ between DRAM technologies. Here it does not differ either, so the same arguments are unavailable — and one more is unavailable that was available before.
| Claim | Why it is not available |
|---|---|
| “It has lower latency” | identical cell, restore, row buffer and constraint classes |
| “It has a simpler controller” | §7: obligations are added, as in 31.2 |
| “It uses a different surface” | no — it uses the same perimeter, which is the whole point of §2 |
| “It escapes the pin-count wall” | it multiplies a constant; 26.1 §1 says the exponent is untouched |
| “It is for graphics” | a market label, and §4 shows the deciding quantity is a workload shape that is not confined to one market |
Row three is the structural point and it distinguishes this chapter from 31.3 completely. The previous chapter's technology changed which surface the connections land on, changing an exponent. This one keeps the surface and changes what each connection carries. Those are different kinds of answer to the same problem, and conflating them is the most common error in this comparison.
2. Establishing the Subject — What the Constraint Is
Since nothing in this curriculum owns this technology, derive it rather than describe it. Start from the question it answers.
The problem, stated in 26.1 §1's terms: a perimeter offers 4L connections and the capability to be fed scales as L². If you are committed to the perimeter — because you cannot afford an interposer, or because the memory must be a separate replaceable component, or because the volume does not justify advanced packaging — then the only remaining variable is what each connection carries.
carried bandwidth = available_connections x per_signal_rate
31.3 §6 works on the first factor and the SURFACE decides it.
This chapter works on the second, and the question becomes:
what limits the rate one connection can carry?And the answer is electrical, not logical. CURRICULUM-DERIVED from Module 22, which owns all of it: 22.2 owns where reflections come from and how termination mitigates them; 22.3 owns inter-symbol interference and crosstalk; 22.4 owns the effects that appear at the highest rates.
So raising the rate means removing electrical penalties, and the biggest available one is topological. STRUCTURAL, and the argument is worth following because it is the whole technology:
| Topological feature | What it costs electrically | Owner |
|---|---|---|
| A socket or connector | an impedance discontinuity on every signal | 22.2 |
| Multi-drop — several devices on one net | stubs, and loading that varies with population | 22.2 |
| A variable device count | the channel's characteristics change with configuration | 22.3 |
| Long board traces to a module | attenuation and inter-symbol interference | 22.3, 22.4 |
Remove all four and the same signal can be driven faster. That is the trade this technology makes: it buys per-pin rate with topology — a soldered, point-to-point, fixed-population, short-trace channel.
So the derivation runs in the direction people do not expect. It is not a fast memory that happens to be soldered. It is a soldered point-to-point channel, and the rate is what that topology makes possible. Every consequence in §3 follows from the topology, not from the rate.
3. What the Topology Costs, Derived
Each row below is derived from §2's four removals rather than looked up, which is why this chapter can establish the technology without quoting a specification.
| Removal | Direct consequence | Axis |
|---|---|---|
| No socket | not replaceable in the field | A7 |
| Point-to-point, fixed population | capacity is fixed at board design — no adding modules | A7 |
| Short traces | the memory must sit physically adjacent, constraining floorplan and thermals | A7 |
| No multi-drop | one device per channel, so capacity per channel is a device property | A3, A4 |
| Higher per-pin rate | the channel is less reliable per bit, so §8's additions become mandatory | A2 |
| Higher per-pin rate | more calibration, and more of it continuous rather than one-time | A2 |
Row two is the one that changes system design rather than memory design. A fixed population means the capacity decision is taken at board design and never revisited — which is 31.3 §16's irreversibility arriving by a completely different route. CURRICULUM-DERIVED structurally: the on-package technology fixes capacity at assembly; this one fixes it at board layout. Two different technologies, two different mechanisms, the same lost option.
And row five is the one people do not anticipate at all, because it runs against the intuition that a faster interface is simply better. A channel driven harder has less margin per bit — 22.4 owns the effects — so the interface must carry machinery to detect and survive errors that a slower channel did not need. §8 is that list, and it is why the obligation set grows.
4. The Deciding Quantity — Latency Tolerance, Made Arithmetic
The quantity is whether the consumer can keep the pipeline full, and it has a closed form that 30.4 §3 already established in a different context.
CURRICULUM-DERIVED from 30.4 §3 Q3: raising a delay costs latency and costs essentially no sustained bandwidth, and the exception is a requester with too few outstanding transactions to cover the longer pipeline — at which point the delay stops being hidden and starts being serialised.
That exception is this chapter's deciding quantity, so state it as the condition it is:
Let L = the memory's completion latency, in cycles
S = the spacing between successive returns the channel
can sustain, in cycles (the OCCUPANCY, 30.4 §3)
N = the number of transactions the requester can keep
outstanding
the pipeline is FULL -- and therefore latency is INVISIBLE -- iff
N >= ceil( L / S )
DERIVED, and read it as a requirement on the REQUESTER:
the memory states L and S; the consumer must supply N.DERIVED, ILLUSTRATIVE, to show the shape:
ILLUSTRATIVE L = 60 cycles, S = 4 cycles (a burst's occupancy).
N* = ceil(60 / 4) = 15 outstanding transactions
a consumer sustaining 15 or more : latency is invisible, and
raising L to 80 changes NOTHING observable (N* becomes 20).
a consumer sustaining 4 : it sees 60 cycles per access,
serialised, and raising L hurts PROPORTIONALLY.
recompute at S = 2 (a shorter burst on a faster channel):
N* = ceil(60 / 2) = 30
DERIVED, and note the direction: making the CHANNEL faster
RAISES the concurrency the consumer must supply. A faster
memory is harder to keep full, not easier.That last result is the section's deliverable and it is counter-intuitive. Halving the return spacing doubles the outstanding transactions required to hide the same latency. So the technology that buys rate is simultaneously raising the bar its consumer must clear — and a consumer that could hide the latency of a slower channel may not be able to hide the same latency on a faster one.
And it explains why the market label is a proxy. CURRICULUM-DERIVED from 29.5, which owns accelerator access patterns: a consumer that streams predictably can prefetch deeply and supply a large N. The property being selected for is deep, predictable concurrency, and any workload with it qualifies — which is why the same interface reasoning applies to workloads far from the one the name suggests. A latency-bound consumer with a short dependency chain fails the test regardless of its market.
5. What Becomes Affordable to Give Up
Once §4's condition holds, the things a latency-optimised design spends to protect latency are available to spend elsewhere. Each row states what is given up and what the saving buys.
| Given up | Why it is affordable | What it buys |
|---|---|---|
| Low completion latency | §4 — invisible when the pipeline is full | pipeline depth for rate and for error handling |
| Fine access granularity | a streaming consumer wants the whole burst anyway | fewer commands per byte — 12.4's command efficiency |
| A bypass path around the return buffer | nothing needs the first beat early | area, and a shorter critical path at high rate |
| In-order return | a reorder buffer is affordable when latency is hidden | scheduling freedom — 23.4 |
| Replaceability | §3 — the topology is what buys the rate | the rate itself |
| Capacity flexibility | fixed population is inherent to point-to-point | the rate itself |
Row two has a cost that must be stated with it, because it is the one this curriculum measures. CURRICULUM-DERIVED from 12.4, which owns the four efficiency measures and insists they stay distinct: a coarser granularity improves command efficiency and worsens payload efficiency — bytes the requester did not want are still moved and paid for. So it is affordable only for a consumer that wants the whole burst, and a design that coarsens the granularity for a consumer with fine-grained accesses has traded a measure it was winning for one it was not.
Row four is the one §12's defect is about. A reorder buffer is not merely permitted by hidden latency — it is the point of it, because the freedom to return out of order is what lets the scheduler exploit 23.4's heuristics without a latency penalty. And it invalidates an assumption the latency-optimised design was entitled to make.
6. What Must Be Added Back
§3's row five said the channel becomes less reliable per bit. This is that, as an obligation list — and it is why the controller grows rather than shrinks.
| Added obligation | Why the rate forces it | Owner |
|---|---|---|
| Link-level error detection on the data path | a channel with less margin per bit produces errors a slower one did not | 22.4 |
| A retry or correction mechanism, and the state to support it | detection without a response is only a counter | §8 |
| Continuous calibration rather than one-time | drift consumes a larger fraction of a smaller margin | 19.5 |
| Two-axis margining rather than one | the eye is small in both dimensions | 21.4 |
| Per-pin trained state, not per-byte | lane-to-lane variation matters more as the margin shrinks | 21.2 |
| Deeper return tracking, with tags | §5 row four: out-of-order return needs association | 10.5 §9 |
| Thermal awareness | adjacent placement plus high activity concentrates heat | 24.1 §4 |
DERIVED: seven added obligations, and the module's arithmetic now reads deletion, addition, multiplication, addition again — but with a different set added than 31.2's. CURRICULUM-DERIVED from 31.2 §7: the low-power profile added power-state and refresh-ownership obligations. This profile adds integrity and calibration obligations instead. So adds obligations is not one pattern — the added set is the technology's fingerprint, and naming which set was added is more informative than counting them.
Row three deserves its distinction stated, because it is the one that changes the verification plan. CURRICULUM-DERIVED from 19.5, which owns the one-time versus continuous split and what it means for a trained value to become valid or stop being valid: a one-time calibration is a bring-up concern, and a continuous one is a runtime concern that interacts with traffic. So the calibration machinery becomes something the scheduler must accommodate, and 30.6 §8 owns the corresponding failure — the mechanism that exists to prevent drift failure can be the mechanism causing the failure, distinguished by whether the failure rate correlates with the calibration interval.
And row one has a consequence for the error model that is worth stating plainly. An interface that expects occasional errors and survives them is a different design contract from one that treats an error as a defect. Chapter 28.5 owns the ECC visibility problem — on-die correction means the observed structure is not the fault structure — and the same reasoning applies to link-level correction: a corrected error is invisible unless somebody counts it, so the corrected-error counter becomes a first-class debug requirement rather than an optional one. Chapter 30.10 §13 owns that as a design deliverable.
7. The Axes, Complete
The module's seven axes, for the last time, with all four chapters side by side. This table is the module's summary artifact and the reason the axes were fixed in 31.1 §2.
| Axis | vs SRAM | vs LPDDR | vs HBM | vs GDDR |
|---|---|---|---|---|
| A1 cell | DIFFERS — causal | identical | identical | identical |
| A2 obligations | deleted 12 of 13 | added 9 | multiplied by channels | added 7, a different set |
| A3 granularity | one operation | finer | finer per channel | coarser |
| A4 channel shape | port-based | more, narrower | many, narrow, semi-independent | point-to-point, fixed |
| A5 latency | constant | +wake term | same terms | same terms, deliberately traded |
| A6 rung moved | not comparable | 1 and 2 | 1, by an order | 1, by a constant factor |
| A7 coupling | on-die | soldered | on-package, fixed at assembly | soldered, fixed at board design |
Read the A1 row first. It differs in exactly one of the four comparisons. That is the module's central empirical finding: the cell is the most fundamental property a memory has, and it is almost never what the decision turns on. Chapter 31.1 is the exception, and it is the only chapter whose decision has a feasibility stage that the cell alone can close.
And read the A2 row second. Every comparison changes the obligation set, and in four different ways. So how much controller does this choice cost me is the question with the most reliable answer in the module — and it is a question almost never asked in a technology comparison.
8. The Ordering Assumption, in Principle
§5 row four gave up in-order return. State precisely what that invalidates, because §12's defect is the invalidation unnoticed.
CURRICULUM-DERIVED from 10.5 §9, which owns the back-to-back-return investigation and lists five mechanisms by which a read path with a single-transaction assumption fails under concurrency. Its third mechanism is exactly this: returns are interleaved or reordered, and the in-order assumption does not hold in this system.
A LATENCY-OPTIMISED return path is entitled to assume:
returns arrive in the order the requests were issued
so association is FREE -- a FIFO suffices, and the head of the
FIFO is the request the next return belongs to.
A THROUGHPUT-OPTIMISED return path gives that up deliberately,
because reordering is what hidden latency BUYS (§5). So:
association must be by TAG, not by POSITION
and the FIFO that was correct becomes a misattribution engine:
it will pair every return with SOME request, and the pairing
will be wrong, and nothing will report it.The failure has no error signal, which makes it the fourth instance in this module of that shape — after 31.2 §7's PASR mask, 31.3 §12's straddling burst, and 30.4 §5's missed write deadline. A position-based association under reordering returns data; it simply returns the wrong data to the wrong requester.
And the configuration dependence is the hazard. With a shallow, bypass-equipped, in-order return path, position-based association is correct. With a deep reorder buffer it is not — and the parameter that flips it is the one a designer changes to improve throughput, which is a performance change nobody expects to affect correctness. Third instance of this module's signature bug class, and §12 is the code.
9. Four Technologies, One Stack
The bottom two layers are the module's conclusion. The access contract is identical across three of the four comparisons, and the cell across three of the four — so a comparison that begins at the cell has begun at the layer least likely to decide anything. Every one of these four decisions was made higher up the stack, and 31.1 §2's axes exist to say where.
10. RTL — One Return Path, Two Objectives
The comparative block for this chapter, and the parameter is the objective rather than a size. LATENCY_CRITICAL decides whether the path carries a bypass and a shallow in-order buffer, or a deep reorder buffer with tag-based association — §5's list and §8's requirement, in one module.
// ---------------------------------------------------------------------
// read_return_path -- the comparative block of §10.
//
// CLASSIFICATION: synthesisable, ILLUSTRATIVE parameter values, and
// CORRECT as written. The intentionally defective block is §11.
//
// WHAT IT IS: the read return path under either objective. The
// generate boundaries are §5's "affordable to give up" list: the
// latency-critical arm carries a bypass and assumes in-order return;
// the throughput arm carries a deep tagged buffer and assumes
// nothing about order.
//
// WHY IT EXISTS HERE: §5 claims that hidden latency makes a reorder
// buffer affordable, and §8 that it simultaneously invalidates
// position-based association. Both claims are structural, and a
// generate boundary is how a structural claim is made checkable.
//
// HOW TO RUN IT: elaborate both arms and present returns OUT OF
// ORDER.
// EXPECTED RESULT: the throughput arm associates correctly by tag;
// the latency-critical arm REFUSES the stimulus via an assertion,
// because out-of-order return is outside its stated contract rather
// than a case it handles badly.
// EXPECTED TRACE: resp_tag must equal the tag of the request whose
// data is being returned, in both arms, for every return.
//
// SYNTHESIS: the latency-critical arm is a small FIFO plus a bypass
// mux. The throughput arm is a tag-indexed buffer of OUTSTANDING
// entries plus a valid vector.
//
// LIMITATIONS: models ASSOCIATION and BUFFERING. It does not model
// link-level error detection or retry -- §6 lists those as added
// obligations and 22.4 owns the electrical reason, and building a
// retry engine here would rebuild a subject this chapter does not
// own. That omission is STATED and is not the bug.
// ---------------------------------------------------------------------
module read_return_path #(
// The OBJECTIVE parameter. It changes the CONTRACT (what may be
// assumed about return order), so 31.1 §12's warning applies at
// full force: the obligation must be re-derived, not inherited.
parameter bit LATENCY_CRITICAL = 1'b0,
parameter int OUTSTANDING = 64,
parameter int DATA_W = 256,
// COUNT versus INDEX, and both appear here for different things.
// TAG_W indexes the OUTSTANDING tag space, so $clog2(OUTSTANDING)
// is correct -- tags run 0 .. OUTSTANDING-1.
parameter int TAG_W = $clog2(OUTSTANDING),
// OCC_W must REPRESENT an occupancy of 0 .. OUTSTANDING inclusive,
// so it needs $clog2(OUTSTANDING + 1). Using TAG_W here would
// wrap a full buffer to empty, and §11 is a block that makes a
// related mistake in a place where it is harder to see.
parameter int OCC_W = $clog2(OUTSTANDING + 1)
)(
input logic clk,
input logic rst_n,
// Issue side.
input logic req_fire,
input logic [TAG_W-1:0] req_tag,
// Device side. `ret_tag` is meaningful only where the device
// returns a tag; the latency-critical arm ignores it by contract.
input logic ret_valid,
input logic [TAG_W-1:0] ret_tag,
input logic [DATA_W-1:0] ret_data,
// Requester side.
output logic resp_valid,
output logic [TAG_W-1:0] resp_tag,
output logic [DATA_W-1:0] resp_data,
input logic resp_ready,
output logic [OCC_W-1:0] occupancy,
output logic overflow,
output logic [7:0] added_obligations
);
initial begin
if (OUTSTANDING < 2)
$fatal(1, "read_return_path: OUTSTANDING must be >= 2");
if ((OUTSTANDING & (OUTSTANDING - 1)) != 0)
$fatal(1, "read_return_path: OUTSTANDING must be a power of two for a tag-indexed buffer");
// §4's condition, surfaced at elaboration as a WARNING rather
// than a fatal, because it is a property of the CONSUMER and this
// block cannot know it. What it can do is refuse to pretend: a
// throughput arm with a shallow buffer cannot hide any latency,
// and saying so at elaboration is cheaper than measuring it.
if (!LATENCY_CRITICAL && OUTSTANDING < 8)
$warning("read_return_path: throughput objective with OUTSTANDING = %0d cannot hide meaningful latency (§4)",
OUTSTANDING);
end
generate
// ===================================================================
// THROUGHPUT OBJECTIVE. Latency is hidden (§4), so reordering is
// AFFORDABLE (§5 row four) -- and association must therefore be by
// TAG (§8). The buffer is indexed by tag, which is what makes
// out-of-order return a non-event rather than a special case.
// ===================================================================
if (!LATENCY_CRITICAL) begin : g_throughput
logic [DATA_W-1:0] buf_data [OUTSTANDING];
logic buf_full [OUTSTANDING]; // data has arrived
logic buf_live [OUTSTANDING]; // tag is outstanding
logic [OCC_W-1:0] occ;
logic [TAG_W-1:0] drain_ptr;
always_ff @(posedge clk) begin
if (!rst_n) begin
for (int i = 0; i < OUTSTANDING; i++) begin
buf_full[i] <= 1'b0;
buf_live[i] <= 1'b0;
end
occ <= '0;
drain_ptr <= '0;
overflow <= 1'b0;
end else begin
// Issue: the tag becomes outstanding. A tag reissued while
// still live is a requester error, and it is flagged rather
// than silently overwritten -- 7.4's discipline that a
// confident wrong answer is worse than a reported unknown.
if (req_fire) begin
if (buf_live[req_tag]) overflow <= 1'b1;
buf_live[req_tag] <= 1'b1;
buf_full[req_tag] <= 1'b0;
if (occ != {OCC_W{1'b1}}) occ <= occ + 1'b1;
end
// Return: land the data at ITS OWN TAG. Order is irrelevant,
// which is the whole point of this arm.
if (ret_valid) begin
buf_data[ret_tag] <= ret_data;
buf_full[ret_tag] <= 1'b1;
end
// Drain: present completed entries to the requester. The
// pointer sweeps the tag space rather than assuming the next
// completion is the next tag.
if (resp_valid && resp_ready) begin
buf_full[drain_ptr] <= 1'b0;
buf_live[drain_ptr] <= 1'b0;
if (occ != '0) occ <= occ - 1'b1;
end
if (!(buf_live[drain_ptr] && buf_full[drain_ptr]))
drain_ptr <= drain_ptr + 1'b1;
end
end
assign resp_valid = buf_live[drain_ptr] && buf_full[drain_ptr];
assign resp_tag = drain_ptr;
assign resp_data = buf_data[drain_ptr];
assign occupancy = occ;
assign added_obligations = 8'd7; // §6's count
// ===================================================================
// LATENCY-CRITICAL OBJECTIVE. A bypass so the first beat is not
// delayed by a buffer, and a shallow in-order FIFO. Position-based
// association is CORRECT HERE, and §8 states exactly why: the
// contract includes in-order return, so position carries the
// association. §12 is what happens when this code is reused where
// that contract no longer holds.
// ===================================================================
end else begin : g_latency
logic [DATA_W-1:0] fifo_data [OUTSTANDING];
logic [TAG_W-1:0] fifo_tag [OUTSTANDING];
logic [OCC_W-1:0] wr_ptr, rd_ptr, occ;
logic bypass;
// The bypass: when nothing is queued and the requester is ready,
// the return goes straight through. This is what the throughput
// arm gives up (§5 row three) and it is worth area only when the
// first beat's arrival time is observable.
assign bypass = ret_valid && (occ == '0) && resp_ready;
always_ff @(posedge clk) begin
if (!rst_n) begin
wr_ptr <= '0; rd_ptr <= '0; occ <= '0; overflow <= 1'b0;
end else begin
if (ret_valid && !bypass) begin
if (occ == OUTSTANDING[OCC_W-1:0]) overflow <= 1'b1;
else begin
fifo_data[wr_ptr[TAG_W-1:0]] <= ret_data;
fifo_tag[wr_ptr[TAG_W-1:0]] <= ret_tag;
wr_ptr <= wr_ptr + 1'b1;
occ <= occ + 1'b1;
end
end
if (resp_valid && resp_ready && !bypass) begin
rd_ptr <= rd_ptr + 1'b1;
occ <= occ - 1'b1;
end
end
end
assign resp_valid = bypass || (occ != '0);
assign resp_data = bypass ? ret_data : fifo_data[rd_ptr[TAG_W-1:0]];
// Even here the TAG is carried rather than reconstructed from
// position. It costs TAG_W bits per entry and it is what makes
// §14's association property writable at all -- a path that
// discards the tag cannot be checked against one.
assign resp_tag = bypass ? ret_tag : fifo_tag[rd_ptr[TAG_W-1:0]];
assign occupancy = occ;
assign added_obligations = 8'd0;
end
endgenerate
endmoduleThe latency-critical arm carries the tag even though position would serve. That is a deliberate design choice and it is the block's quietest lesson: carrying the tag costs TAG_W bits per entry and makes §14's association property writable in both arms. A path that reconstructs the tag from position cannot be checked against an independent one — which is 27.3's independence requirement applied to a design signal rather than to a monitor.
11. RTL Review — The Associator
The intended contract:
- Every return must be associated with the request whose data it carries.
- The association must be by tag, not by position, unless the contract guarantees in-order return.
- A return carrying a tag that is not outstanding is an error, and must be reported rather than absorbed.
resp_tagmust equal the tag of the request whose dataresp_dataholds — for every return, in every configuration.- Occupancy must never exceed
OUTSTANDING, and an attempt must setoverflow.
// ---------------------------------------------------------------------
// return_associator -- INTENTIONALLY DEFECTIVE, for review (§11).
//
// CLASSIFICATION: synthesisable, ILLUSTRATIVE, and CONTAINS A BUG.
//
// WHAT IT IS MEANT TO DO: the five-clause contract above -- pair each
// return with its request.
//
// WHY IT EXISTS HERE: §8 establishes that a latency-optimised path is
// ENTITLED to associate by position, and that a throughput path which
// reorders deliberately is not. This block is that entitlement
// carried across a parameter change, and it is the third instance in
// this module of the same bug class -- 31.1 §12 (a contract
// parameter), 31.2 §12 (a duration), 31.3 §12 (a granularity).
//
// It is also a sixth mechanism for 10.5 §9's back-to-back-return
// investigation, whose third listed mechanism is exactly "returns
// are reordered and the in-order assumption does not hold". That
// chapter owns the DEBUG method; this block is the DESIGN that
// produces the symptom.
//
// HOW TO RUN IT: issue tags 0,1,2 and return them 2,0,1.
// EXPECTED RESULT under clause 4: resp_tag follows the DATA.
// EXPECTED TRACE: the return carrying tag 2's data must be presented
// with resp_tag = 2, not with resp_tag = 0.
//
// SYNTHESIS: two pointers and a data FIFO.
// LIMITATIONS: association only. Buffering policy is §10's.
// ---------------------------------------------------------------------
module return_associator #(
parameter int OUTSTANDING = 64,
parameter int DATA_W = 256,
parameter int TAG_W = $clog2(OUTSTANDING),
parameter int OCC_W = $clog2(OUTSTANDING + 1)
)(
input logic clk,
input logic rst_n,
input logic req_fire,
input logic [TAG_W-1:0] req_tag,
input logic ret_valid,
input logic [TAG_W-1:0] ret_tag, // on the interface, and...
input logic [DATA_W-1:0] ret_data,
output logic resp_valid,
output logic [TAG_W-1:0] resp_tag,
output logic [DATA_W-1:0] resp_data,
input logic resp_ready,
output logic [OCC_W-1:0] occupancy,
output logic overflow,
output logic assoc_error
);
// The outstanding-request queue, in ISSUE order.
logic [TAG_W-1:0] pend_tag [OUTSTANDING];
logic [DATA_W-1:0] ret_buf [OUTSTANDING];
logic [OCC_W-1:0] head, tail, occ;
always_ff @(posedge clk) begin
if (!rst_n) begin
head <= '0; tail <= '0; occ <= '0;
overflow <= 1'b0; assoc_error <= 1'b0;
end else begin
// Record the issue order. Correct, and necessary.
if (req_fire) begin
if (occ == OUTSTANDING[OCC_W-1:0]) overflow <= 1'b1;
else begin
pend_tag[tail[TAG_W-1:0]] <= req_tag;
tail <= tail + 1'b1;
occ <= occ + 1'b1;
end
end
// The return. Data is stored at the HEAD of the issue queue --
// that is, the return is assumed to belong to the OLDEST
// outstanding request.
if (ret_valid) begin
ret_buf[head[TAG_W-1:0]] <= ret_data; // <-- THE DEFECT
end
if (resp_valid && resp_ready) begin
head <= head + 1'b1;
occ <= occ - 1'b1;
end
end
end
assign resp_valid = (occ != '0);
assign resp_tag = pend_tag[head[TAG_W-1:0]];
assign resp_data = ret_buf[head[TAG_W-1:0]];
assign occupancy = occ;
endmoduleBefore reading on: which clause, and what does ret_tag do in this module?
12. The Defect — Position Where the Contract Said Tag
The violated clause is 2, and clause 4 falls with it. ret_tag is declared on the port list and never read. The return's data is filed at the head of the issue-order queue, so the association is by position — correct only under the in-order guarantee that §8 says the throughput objective deliberately gives up.
The unread input is the static tell again — the third time in this module and the fifth in the curriculum, after 30.7 §9's trained offset, 30.8 §10's outstanding flag and 31.1 §12's access outcome. CURRICULUM-DERIVED from 30.9 §3: lint finds a declared-and-unread input before simulation starts, and naming the cheapest tool that would have caught a bug is a legitimate verification answer.
The trace, and it is the clearest failure in the module:
ILLUSTRATIVE. Issue tags 0, 1, 2 in that order. The device
returns them REORDERED as 2, 0, 1 -- which the throughput
objective explicitly permits (§5 row four).
issue: pend_tag[0]=0 pend_tag[1]=1 pend_tag[2]=2 occ=3
return tag 2 (data D2) : ret_buf[head=0] <= D2
return tag 0 (data D0) : ret_buf[head=0] <= D0 overwritten!
return tag 1 (data D1) : ret_buf[head=0] <= D1 overwritten!
drain: resp_tag = pend_tag[0] = 0, resp_data = D1
so request 0 is answered with request 1's data. Then head
advances and request 1 is answered with whatever landed next.
TWO failures at once, and the second is worse:
- MISATTRIBUTION: every response carries the wrong data.
- LOSS: three returns landed in one slot, so two were
destroyed. The data is not merely misrouted; it is gone.Clause 3 is also silently unmet. assoc_error is declared and never driven, so a return for a tag that was never outstanding is absorbed without a report — and that is the observable which would have made the misattribution diagnosable in a lab.
Now the review question: which parameter change makes a working design violate this, and why is that change not reviewed for correctness?
The change is enabling reordering — or equivalently, increasing OUTSTANDING past the point where the device starts returning out of order. That is a performance change. It is proposed to raise throughput, it is measured in bandwidth, it is reviewed by whoever owns performance, and its correctness consequence is in a different module that nobody re-read.
A performance parameter that changes what may be ASSUMED is a contract change wearing a performance parameter's clothing. The review question for any such change is not is it faster but which assumption did it invalidate, and who was relying on it.
And this is why the module's three defects are the same bug class with three different triggers: a contract parameter (31.1 §12), a residency (31.2 §12), a granularity (31.3 §12), and now a concurrency limit. In every case the code was correct before the change and the change was reviewed against the wrong criterion.
The correction, and the two additions are separate obligations:
// CORRECTED. Clause 2: file the data at ITS OWN TAG, so return
// order is irrelevant. This is the same structure as §10's
// throughput arm, which is the point -- the correct design
// already existed in the module and this block did not use it.
if (ret_valid) begin
if (!pend_live[ret_tag]) begin
// Clause 3: a return for a tag that is not outstanding is
// an ERROR and must be REPORTED. Absorbing it is what
// makes the misattribution undiagnosable in a lab --
// 30.10 §13's argument that a debug observable is a design
// deliverable rather than an optional extra.
assoc_error <= 1'b1;
end else begin
ret_buf[ret_tag] <= ret_data;
ret_full[ret_tag] <= 1'b1;
end
end
// And the drain must present a COMPLETED entry rather than the
// oldest issued one, because under reordering the oldest issue
// is not necessarily the first completion.
// resp_valid = pend_live[drain_ptr] && ret_full[drain_ptr];
// resp_tag = drain_ptr;
// resp_data = ret_buf[drain_ptr];Two interface additions the correction requires, stated rather than assumed: a pend_live vector — one bit per tag, marking the tag outstanding — and a ret_full vector marking data arrived. Neither existed in the defective block, because position-based association needs neither. A correction that silently references state the module does not have is not a correction, and the same note was needed in 31.1 §12.
The structural finding, and it is the module's last: the defective block and the correct one differ by which index is used to write one array. head against ret_tag. One identifier. That is what makes this class of bug worth a chapter: the wrong version is not badly written, it is a smaller amount of correct-looking code that was right under an assumption nobody restated when it stopped holding.
13. RTL — Measuring the Premise
§4's condition is a claim about the consumer, and the whole trade rests on it. A design that gave up latency and then discovered its requester cannot keep the pipeline full has traded away something it needed for something it cannot use. This block measures whether the premise holds.
// ---------------------------------------------------------------------
// latency_tolerance_monitor -- verification/telemetry, CORRECT as written.
//
// CLASSIFICATION: synthesisable telemetry. Drives nothing.
//
// WHAT IT DOES: measures the outstanding-transaction count over a
// window against ceil(L / S), reporting whether the pipeline was
// actually full often enough for §4's condition to hold.
//
// WHY IT EXISTS HERE: every trade in §5 is licensed by §4's
// condition, and §4's condition is about the REQUESTER. This is the
// instrument that checks the licence. 30.8 §13 makes the same
// argument for the bandwidth ladder: rung 1 moving does not move
// rung 5, and only a measurement distinguishes them.
//
// HOW TO RUN IT: run the real access pattern and read
// `frac_full_num` / `frac_full_den`.
// EXPECTED RESULT: a deeply pipelined streaming consumer reports a
// high fraction; a dependency-chained one reports near zero, and
// that is the finding -- the technology's premise does not hold for
// that consumer.
//
// SYNTHESIS: two counters, a max tracker, one comparator.
//
// LIMITATIONS: measures CONCURRENCY, not throughput. A consumer can
// satisfy §4's condition and still be limited elsewhere -- 30.8's
// gaps 2-to-3 and 4-to-5 -- so a high fraction here licenses the
// latency trade and proves nothing about achieved bandwidth.
// ---------------------------------------------------------------------
module latency_tolerance_monitor #(
parameter int WIN = 65536,
parameter int OUTSTANDING = 64,
// L and S from §4. ILLUSTRATIVE, and supplied rather than derived
// because S depends on the burst and L on the configuration --
// 30.4 §2's unit discipline means neither can be assumed here.
parameter int LAT_CYCLES = 60,
parameter int RET_SPACING = 4,
// DERIVED at elaboration: N* = ceil(L / S). Integer ceiling,
// written so it is obviously a ceiling rather than a truncation --
// a floor here would UNDERSTATE the requirement and license a
// trade the consumer cannot support.
parameter int N_STAR = (LAT_CYCLES + RET_SPACING - 1) / RET_SPACING,
// COUNT, not INDEX: occupancy ranges 0 .. OUTSTANDING inclusive.
parameter int OCC_W = $clog2(OUTSTANDING + 1),
parameter int CNT_W = $clog2(WIN + 1)
)(
input logic clk,
input logic rst_n,
input logic req_fire,
input logic resp_fire,
input logic win_tick,
output logic [OCC_W-1:0] r_max_outstanding,
output logic [CNT_W-1:0] frac_full_num, // cycles with occ >= N*
output logic [CNT_W-1:0] frac_full_den, // cycles with occ > 0
output logic premise_holds,
output logic result_valid,
output logic underflow
);
initial begin
if (RET_SPACING < 1) $fatal(1, "latency_tolerance_monitor: RET_SPACING >= 1");
if (LAT_CYCLES < 1) $fatal(1, "latency_tolerance_monitor: LAT_CYCLES >= 1");
// The structural warning §4 earns: if the buffer cannot hold N*
// transactions, the premise CANNOT hold however well the
// requester behaves, and that is a design error rather than a
// workload property. Flagged at elaboration because it is a
// static fact -- 30.9 §3's cheapest-tool rule.
if (OUTSTANDING < N_STAR)
$fatal(1, "latency_tolerance_monitor: OUTSTANDING (%0d) < N* (%0d): the pipeline cannot be filled (§4)",
OUTSTANDING, N_STAR);
end
logic [OCC_W-1:0] occ, mx;
logic [CNT_W-1:0] c_full, c_busy;
always_ff @(posedge clk) begin
if (!rst_n) begin
occ <= '0; mx <= '0; c_full <= '0; c_busy <= '0;
r_max_outstanding <= '0;
frac_full_num <= '0; frac_full_den <= '0;
premise_holds <= 1'b0; result_valid <= 1'b0; underflow <= 1'b0;
end else if (win_tick) begin
r_max_outstanding <= mx;
frac_full_num <= c_full;
frac_full_den <= c_busy;
// The verdict, cross-multiplied so no divider is needed:
// the premise holds when the pipeline was full for at least
// three quarters of the BUSY cycles. The threshold is a POLICY
// and it is stated here rather than hidden -- 24.2 §5's
// practice of naming a policy input as a policy input.
premise_holds <= (c_full * 4) >= (c_busy * 3);
result_valid <= 1'b1;
mx <= '0; c_full <= '0; c_busy <= '0;
end else begin
result_valid <= 1'b0;
// Occupancy tracking. The two events can coincide, so the
// combined case is handled explicitly rather than by two
// independent if-statements that would race.
if (req_fire && !resp_fire) begin
if (occ != OUTSTANDING[OCC_W-1:0]) occ <= occ + 1'b1;
end else if (resp_fire && !req_fire) begin
// A response with nothing outstanding is an ERROR, not a
// saturating subtract. Absorbing it would make the counter
// drift silently and every figure below it meaningless --
// 30.8 §11's rule that an instrument which can report an
// impossible value has never been checked.
if (occ == '0) underflow <= 1'b1;
else occ <= occ - 1'b1;
end
if (occ > mx) mx <= occ;
if (occ >= N_STAR[OCC_W-1:0] && c_full != {CNT_W{1'b1}})
c_full <= c_full + 1'b1;
if (occ != '0 && c_busy != {CNT_W{1'b1}})
c_busy <= c_busy + 1'b1;
end
end
endmoduleThe denominator is busy cycles, not window cycles, and that choice is the block's substance. Measuring against the whole window conflates the consumer cannot keep the pipeline full with the consumer had nothing to do — which is exactly the denominator error 30.8 §10 diagnoses, where an instrument reported 400% efficiency because it counted arrivals rather than outstanding work. This block avoids that specific mistake by construction, and says so.
And underflow exists because a drifting occupancy counter invalidates every other output. CURRICULUM-DERIVED from 30.8 §11: a measurement that can report an impossible value has never been checked, so the impossible case is reported rather than clamped.
14. SVA Review — What Survives a Contract Change
The property written for §11's block, and it is a genuinely good property:
// Offered as "proves every return is associated with a request".
// Clause 1, and it PASSES on the defective block.
property p_response_has_a_request;
@(posedge clk) disable iff (!rst_n)
resp_valid |-> (occupancy != '0);
endproperty
assert property (p_response_has_a_request)
else $error("a response was presented with nothing outstanding");
// And the in-order association property, which was CORRECT in the
// latency-optimised design this code came from.
property p_response_follows_issue_order;
@(posedge clk) disable iff (!rst_n)
(resp_valid && resp_ready) |-> (resp_tag == pend_tag[head]);
endproperty
assert property (p_response_follows_issue_order)
else $error("response tag did not follow issue order");Q. Both pass. What is wrong with the second one in particular?
It is 30.9 §6's variety 10 in its purest form yet. Under the latency-critical objective the contract includes in-order return, so the property is sound. Under the throughput objective the contract excludes it — and the property now asserts that responses must follow issue order, which is exactly the behaviour the objective was chosen to avoid. A corrected design that returns out of order fails it.
So the property does not merely miss the defect. It forbids the fix. And the first property is variety 2: it proves a response had some outstanding request and never mentions which.
The general repair, and it is the module's closing verification result:
When a parameter changes what may be ASSUMED, the assertion set partitions rather than generalises. Properties that encode the old assumption must be guarded by the configuration that licenses them, and a property with no such guard is claiming to hold in a configuration where it is false.
What actually covers clauses 1 to 4 across both objectives:
// Clause 4, and it is the property that is sound in BOTH arms
// because it names the DATA's tag rather than a position. The
// reference function is an independent model of the association --
// 27.3's independence requirement, and 30.6 §11's rule that a
// comparative obligation needs a model rather than a property.
property p_response_tag_matches_its_data;
@(posedge clk) disable iff (!rst_n)
(resp_valid && resp_ready) |->
(resp_data == ref_expected_data(resp_tag));
endproperty
assert property (p_response_tag_matches_its_data)
else $error("response data does not belong to the response tag");
// Clause 2, guarded by the configuration that licenses it. This is
// the repair for variety 10: the parameter sits in the ANTECEDENT
// as a condition on which contract is in force, and the claim
// inside is unchanged in meaning -- 31.1 §14's test, passed.
property p_in_order_only_when_licensed;
@(posedge clk) disable iff (!rst_n)
(LATENCY_CRITICAL && resp_valid && resp_ready) |->
(resp_tag == pend_tag[head]);
endproperty
assert property (p_in_order_only_when_licensed)
else $error("in-order association violated in the arm that guarantees it");
// Clause 3. A return for a tag that is not outstanding must be
// REPORTED. The defective block leaves assoc_error undriven, so
// this property is what turns an undriven output into a failure.
property p_unknown_tag_is_reported;
@(posedge clk) disable iff (!rst_n)
(ret_valid && !pend_live[ret_tag]) |-> assoc_error;
endproperty
assert property (p_unknown_tag_is_reported)
else $error("a return for a non-outstanding tag was absorbed silently");
// Clause 5, both directions. Occupancy is a derived count, so its
// RANGE is asserted -- 30.8 §11's rule, and the direction matters:
// an understated occupancy licenses an overflow.
property p_occupancy_in_range;
@(posedge clk) disable iff (!rst_n)
occupancy <= OUTSTANDING;
endproperty
assert property (p_occupancy_in_range)
else $error("occupancy exceeded OUTSTANDING");
property p_overflow_is_reported;
@(posedge clk) disable iff (!rst_n)
(req_fire && occupancy == OUTSTANDING) |-> overflow;
endproperty
assert property (p_overflow_is_reported)
else $error("an issue beyond OUTSTANDING did not set overflow");
// No data may be OVERWRITTEN before it is delivered. This is the
// second half of §12's failure -- the loss, which the
// misattribution properties above do not catch, because a
// destroyed return produces no wrong value at the interface, only
// a missing one.
property p_no_return_overwritten;
@(posedge clk) disable iff (!rst_n)
(ret_valid && ret_full[ret_tag]) |-> assoc_error;
endproperty
assert property (p_no_return_overwritten)
else $error("a return overwrote undelivered data for the same tag");
// §4's premise, asserted where it is a DESIGN fact rather than a
// workload one: the buffer must be able to hold N*.
property p_buffer_can_hold_n_star;
@(posedge clk) disable iff (!rst_n)
OUTSTANDING >= N_STAR;
endproperty
assert property (p_buffer_can_hold_n_star)
else $error("the return buffer is too shallow to fill the pipeline");
// ---- Covers. Per configuration, per order, and per depth.
// The two objectives -- 31.1 §14's configuration rule.
cover property (@(posedge clk) disable iff (!rst_n)
resp_valid && LATENCY_CRITICAL);
cover property (@(posedge clk) disable iff (!rst_n)
resp_valid && !LATENCY_CRITICAL);
// OUT-OF-ORDER return, which is the antecedent the whole defect
// lives behind. In-order stimulus makes every property above hold
// on the defective block, so this cover is the one that matters --
// and an environment that models the device as in-order will
// never reach it, however long it runs.
cover property (@(posedge clk) disable iff (!rst_n)
ret_valid && (ret_tag != pend_tag[head]));
// And the DEPTH regime, because §4 shows the requirement scales:
// a pipeline that is full is a different test from one that is
// merely non-empty, and only the first exercises reordering
// pressure.
cover property (@(posedge clk) disable iff (!rst_n)
occupancy >= N_STAR);
cover property (@(posedge clk) disable iff (!rst_n)
occupancy == OUTSTANDING);The out-of-order cover is the module's fourth distinct coverage-reachability problem, and the set is now worth stating as one rule.
| Chapter | The defect scales with | So the cover must be on |
|---|---|---|
| 31.1 §14 | a configuration | each configuration |
| 31.2 §14 | a duration | the duration, thresholded by the failure mechanism |
| 31.3 §15 | an index space | each index |
| This chapter | a return ORDER | the reordered case, which an in-order model never produces |
The coverage item must be on the dimension the defect scales with — and when that dimension is a property of the environment's MODEL rather than of the stimulus, no amount of running longer reaches it.
That last clause is the sharpest of the four. A longer run reaches more configurations, more durations and more indices. It never reaches an out-of-order return if the device model returns in order, because the unreached case is not rare — it is absent by construction. CURRICULUM-DERIVED from 27.3's independence requirement and 30.9 §12's escape taxonomy, whose fifth mechanism — the stimulus never reached the state — is the only one that more coverage addresses, and this is a case where the stimulus cannot reach it until the model is changed.
15. What the Return Path's Assertions Prove
§14 reviewed the associator and left §10's block unasserted. The gap matters here for a reason specific to this chapter: §10 is the module's only comparative block whose two arms have different interfaces in use — the throughput arm reads ret_tag, the latency arm reads it only to carry it — so a property set that does not distinguish the arms will either be unsound in one or vacuous in the other.
// ---- The BYPASS, which exists only in the latency arm and is
// §5 row three's give-up made concrete. Two obligations, and
// they are separate: the bypass must be CORRECT when taken,
// and it must not be taken when it would reorder.
property p_bypass_preserves_association;
@(posedge clk) disable iff (!rst_n)
(LATENCY_CRITICAL && bypass) |->
(resp_tag == ret_tag && resp_data == ret_data);
endproperty
assert property (p_bypass_preserves_association)
else $error("the bypass presented a tag or data that was not the return's");
// A bypass while the FIFO is non-empty would deliver a NEWER
// return ahead of an older queued one -- reordering, inside the
// arm whose whole contract is that it does not reorder. This is
// the property that makes the bypass safe rather than merely fast.
property p_no_bypass_when_queued;
@(posedge clk) disable iff (!rst_n)
(LATENCY_CRITICAL && bypass) |-> (occupancy == '0);
endproperty
assert property (p_no_bypass_when_queued)
else $error("bypass taken with data queued: the in-order contract is broken");
// ---- Occupancy must agree with the events that change it. A
// derived count, so its RANGE is asserted (30.8 §11) and its
// ARITHMETIC is asserted separately -- a counter can be in
// range and still have drifted.
property p_occupancy_tracks_events;
@(posedge clk) disable iff (!rst_n)
occupancy == ($past(occupancy)
+ (($past(req_fire) && !$past(resp_valid && resp_ready)) ? 1 : 0)
- ((!$past(req_fire) && $past(resp_valid && resp_ready)) ? 1 : 0));
endproperty
assert property (p_occupancy_tracks_events)
else $error("occupancy drifted from the issue and completion events");
// ---- The throughput arm's defining property, and the one that is
// FALSE in the other arm -- so it is guarded by the
// configuration, per §14's repair for variety 10. A tag's
// data must be filed at that tag, independent of arrival
// order, which is the structural difference §8 names.
property p_tag_indexed_when_reordering;
@(posedge clk) disable iff (!rst_n)
(!LATENCY_CRITICAL && ret_valid) |=> buf_full[$past(ret_tag)];
endproperty
assert property (p_tag_indexed_when_reordering)
else $error("a return did not land at its own tag in the reordering arm");
// ---- A tag reissued while still outstanding is a REQUESTER error
// and must be reported, not absorbed. 7.4's discipline: a
// confident wrong answer is worse than a reported unknown,
// and silently overwriting a live entry produces exactly the
// loss failure §12's second half describes.
property p_live_tag_reissue_is_reported;
@(posedge clk) disable iff (!rst_n)
(!LATENCY_CRITICAL && req_fire && buf_live[req_tag]) |=> overflow;
endproperty
assert property (p_live_tag_reissue_is_reported)
else $error("a live tag was reissued without report");
// ---- Covers. Per arm, and then per the FEATURE each arm has that
// the other does not -- because a cover on the arm alone does
// not prove the arm's distinguishing path was exercised.
// The bypass, taken. Absent from the throughput arm by
// construction, so this must go DEAD there and be LIVE here --
// two different instruments, 30.10 §12's distinction.
cover property (@(posedge clk) disable iff (!rst_n)
LATENCY_CRITICAL && bypass);
// The bypass NOT taken because data was queued: the path that
// makes p_no_bypass_when_queued non-trivial.
cover property (@(posedge clk) disable iff (!rst_n)
LATENCY_CRITICAL && ret_valid && (occupancy != '0));
// A return landing at a tag that is NOT the drain pointer, which
// is the reordering arm's distinguishing event and the antecedent
// §14's out-of-order cover watches from the other side.
cover property (@(posedge clk) disable iff (!rst_n)
!LATENCY_CRITICAL && ret_valid && (ret_tag != drain_ptr));
// A full buffer in the reordering arm -- §4's N* condition
// actually reached, rather than merely permitted by the parameter.
cover property (@(posedge clk) disable iff (!rst_n)
!LATENCY_CRITICAL && (occupancy == OUTSTANDING));p_no_bypass_when_queued is the property worth carrying out of this section, because it is the one a designer adding a bypass for speed would not think to write. A bypass is a reordering mechanism — it lets a newer return overtake older queued ones — so the arm whose contract forbids reordering must forbid the bypass in exactly the circumstances where it would reorder. The optimisation and the contract collide, and only a property states where.
And the two dead-or-live covers make the arm distinction auditable. CURRICULUM-DERIVED from 30.10 §12: a cover that must stay live and one that must go dead are different instruments, and naming which you are writing is the senior move. The bypass cover must be live in one arm and dead in the other, and a regression where it is dead in both has not run the latency arm at all.
16. What Would You Measure?
Q. You are considering this technology for a new design. What do you measure, in what order?
| Measurement | What it settles | Cost | Owner |
|---|---|---|---|
| Is the memory required to be field-replaceable? | §3 — closes the question with no further work | hours | §3 |
| Is the capacity requirement settled at board design? | §3 row two — point-to-point fixes it there | hours | §3 |
The requester's sustained outstanding count against N* | §4's premise, which licenses every trade in §5 | days | §4, §13 |
frac_full_num / frac_full_den from §13 on the real pattern | whether the premise holds often enough | days | §13 |
| The requester's access granularity against the burst | §5 row two — payload efficiency, which coarsening costs | days | 12.4 |
| Whether the device model in the environment returns in order | §14 — whether the association was ever tested | hours | §14 |
| The corrected-error counter's existence in the design | §6 — a corrected error is invisible unless counted | hours | 28.5 |
| Thermal headroom at the adjacent placement | §3 row three, and there may be no fan | weeks | 24.1 §4 |
| The five-rung ladder on a prototype | whether rung 1's constant factor moved rung 5 | weeks | 30.8 §3 |
Rows one and two cost hours and can close the question, which is the ordering discipline every chapter in this module has repeated. A replaceability requirement or an unsettled capacity requirement makes the topology unavailable, and no rate argument overrides a topology that is not on offer.
Row three is the premise and row four is its honest form. A peak outstanding count proves the requester can reach N*; the fraction proves it does. CURRICULUM-DERIVED from 23.1, which owns why the average is the wrong statistic — the same argument applies to a maximum, in the opposite direction.
And row six is free and almost never asked. If the environment's device model returns in order, §12's defect class was never testable, and that is a finding available in an afternoon by reading the model. CURRICULUM-DERIVED from 30.9 §12's escape analysis: four of its five mechanisms are plan or review defects that no additional stimulus would have found, and an in-order device model is precisely such a defect.
17. Common Wrong Answers
“It is DDR with the clock turned up.” §2, §6. The rate is bought with topology, and the topology costs replaceability and capacity flexibility while the rate forces seven added obligations back in.
“It escapes the pin-count wall.” §1, and 26.1 §1 owns the correction: a constant factor cannot change the 4/L exponent. It postpones and never removes.
“It uses a different surface, like HBM.” §1, §2. It uses the same perimeter. Changing the surface and changing the rate are different kinds of answer to the same problem.
“It has lower latency because it is faster.” §1, §5. Latency is what it trades away, deliberately, and §4 is the condition under which that is affordable.
“A faster memory is easier to keep full.” §4. Halving the return spacing doubles the outstanding transactions required to hide the same latency. The bar rises with the rate.
“It is for graphics.” §4. A market label. The property being selected for is deep, predictable concurrency, and any workload with it qualifies — while a latency-bound consumer fails regardless of its market.
“Bigger bursts are free for a streaming consumer.” §5. They improve command efficiency and worsen payload efficiency — 12.4 — so they are free only for a consumer that wants the whole burst.
“It has a simpler interface because it drops the socket.” §6. Seven obligations are added: link-level detection, a response to it, continuous calibration, two-axis margining, per-pin trained state, tagged return tracking, and thermal awareness.
“Calibration is a bring-up concern.” §6, and 19.5 owns the one-time-versus-continuous split. Continuous calibration is a runtime concern that the scheduler must accommodate, and 30.6 §8 owns the failure where the mechanism itself is the disturbance.
“Link-level correction makes errors a non-issue.” §6. A corrected error is invisible unless counted, and 28.5 owns why the observed structure is then not the fault structure.
“We can add capacity later.” §3. Point-to-point with a fixed population fixes capacity at board design — a different mechanism from 31.3 §16's assembly, and the same lost option.
“Reordering returns is an optimisation we can add later.” §8, §12. It is a contract change: position-based association becomes wrong, silently, and returns data to the wrong requester.
“A FIFO associates returns with requests.” §8. Only under a guaranteed in-order return. Otherwise it pairs every return with some request and every pairing is wrong.
“ret_tag is on the interface, so the association is tag-based.” §12. Declared and never read — the fifth instance of that tell in the curriculum, and lint finds it before simulation starts.
“The misattribution would show up as wrong data.” §12. Partly: the second failure is loss, because three returns landed in one slot. A destroyed return produces a missing value rather than a wrong one, and §14 needs a separate property for it.
“The association assertion passes.” §14. One of them proves a response had some request, and the other forbids the fix — variety 10 in its purest form.
“We will find it by running the regression longer.” §14. Not if the device model returns in order. The unreached case is absent by construction, not rare.
“The consumer reached N* at peak, so the premise holds.” §15. A maximum proves it can; the busy-cycle fraction proves it does — and 23.1 owns why a single summary statistic is the wrong instrument.
18. Self-Check
-
State which of 31.3 §4's two ways past the pin-count wall this technology takes, and explain in one sentence what that implies about its limit.
-
Derive the four topological removals of §2 and, for each, the electrical penalty it eliminates and the chapter that owns that penalty.
-
From §3, give three consequences of the topology that have nothing to do with speed, and name the axis each belongs to.
-
Write §4's condition from memory. Compute
N*forL = 100, S = 5and forL = 100, S = 2, and state what the second result implies about making a channel faster. -
Give three items from §5's affordable to give up list, and for each the specific cost that must be stated alongside it.
-
Give four items from §6's added back list, and say which one changes the verification plan rather than the design.
-
Reproduce §7's axis table for the A1 and A2 rows across all four comparisons. State the module's central empirical finding in one sentence.
-
Explain why position-based association is correct in one arm of §10 and wrong in the other, and identify the single identifier that differs between §11's defective block and its correction.
-
Explain why
p_response_follows_issue_orderforbids the fix, and state the general repair for a property that encodes a superseded assumption. -
Name the four coverage-reachability problems this module produced, one per chapter, and say which one more running cannot solve and why.
-
You are told the regression is green and the environment's device model returns in order. State what you now know, what you do not, and what you would ask for.
19. Where This Goes — and the Module Closes
The graphics variant buys per-pin rate with topology. It takes the constant-factor route past the pin-count wall, so it postpones and never removes it; the rate is purchased with a soldered point-to-point fixed-population channel, and the purchase price is replaceability and capacity flexibility; the deciding quantity is the consumer's latency tolerance, measured as whether the requester sustains ceil(L/S) transactions outstanding; making the channel faster raises that bar rather than lowering it; and what the rate forces back in — integrity, continuous calibration, per-pin state, tagged association — is why this is not DDR with the clock turned up.
And the module closes on the law it opened with.
A COMPARISON IS NOT TWO DESCRIPTIONS PLACED SIDE BY SIDE. IT IS A DECISION, AND A DECISION HAS A DECIDING QUANTITY.
Four comparisons, four deciding quantities, and not one of them is a specification number.
| Comparison | The deciding quantity | Where it is measured |
|---|---|---|
| vs SRAM | tolerable completion spread, then capacity against a crossover carrying a controller term | the requester's deadline |
| vs LPDDR | duty cycle and idle-duration distribution | the workload |
| vs HBM | connections per unit of capability, which the surface decides | the package |
| vs GDDR — this chapter | latency tolerance, as sustained outstanding transactions | the consumer |
Every one of the four is measured outside the memory. That is the module's single most useful result: a memory-technology decision is not made by comparing memories. It is made by measuring the system that will use one, which is why 31.1 §1's two-stage ordering — feasibility before cost — recurs in every chapter, and why every what would you measure section begins with a row that costs hours and can close the question.
Three structural findings hold across all four.
Axis A1 differs in one comparison out of four. The cell is the most fundamental property a memory has and it is almost never the layer the decision turns on — §9's diagram is that result as a picture, and §7's table as arithmetic.
Every comparison changes the obligation set, in four different ways. Deleted, added, multiplied, and added-with-a-different-set. So how much controller does this cost me has the most reliable answer of any question in the module, and it is the question a technology comparison almost never asks.
And all three defects were the same bug class with different triggers. A contract parameter (31.1 §12), a residency (31.2 §12), a granularity (31.3 §12), and a concurrency limit (§12). In every case the code was correct before the change, the change was proposed for performance, and it was reviewed against the wrong criterion. The one review question that would have caught all four:
Which assumption did this change invalidate, and who was relying on it?
And the module contributed one new entry to 30.9 §6's taxonomy plus one refinement and one rule. Variety 10 — parameter-conditional soundness, where a property is sound under one configuration and, under another, certifies the defect and forbids its fix — three instances, in 31.1 §14, 31.3 §14 and §14. The scale-vacuity refinement of variety 6, from 31.2 §14. And the coverage rule the four chapters converge on: the coverage item must be on the dimension the defect scales with — and when that dimension belongs to the environment's model rather than to its stimulus, running longer never reaches it.
Continue learning
Related tutorials
- Related topic
DDR vs SRAM
SRAM is not faster DRAM: its read is non-destructive, and that one structural difference creates or deletes thirteen controller obligations. The decision has two stages, and admissibility must be settled before capacity.
- Related topic
DDR vs LPDDR
The access contract is identical and all thirteen obligations are shared, so no structural argument separates these two. The deciding quantity is energy under the actual duty cycle and idle-duration distribution, not power and not bandwidth.
- Related topic
DDR vs HBM
Per signal wire DDR carries more bandwidth; HBM wins because it reaches a surface that can host far more wires. The deciding quantity is connections per unit of capability, and a perimeter's ratio falls as one over the side length.
- Related topic
GPUs
One requester population clears the latency-hiding condition by a factor of tens and another cannot clear it at all, and the same controller serves both. They are coupled — the small one gates the large one's work launch — so a throughput-maximising tuning is net negative below a computable launch interval.
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.
