DDR · Module 28
Refresh Issues
Three investigations wearing one name: a missed obligation, late service, or something else that merely correlates with refresh. Separating them is most of the work.
Chapter 28.2 debugged a rule between two commands. A violation there was local: one command, one predecessor, one shared resource, one named parameter.
Refresh has no such shape. A refresh obligation is a rate, not an event. The device requires a certain amount of refresh service over a window, and 15.3 owns the postponement and pull-in allowance that makes when negotiable within bounds.
So there is no single violating command. There is a ledger, a deadline, and a question that splits three ways before any investigation can start:
| The question | What it is really asking |
|---|---|
| Obligation failure | did the device actually go unserved past its deadline? |
| Service failure | was the obligation met, but late, expensively, or unfairly? |
| Correlation | is something else failing, and refresh merely happens to be nearby? |
These are three different investigations, and the third is the one that consumes weeks. A failure that clusters near refresh is not a refresh failure — and the most common mistake in this chapter's subject is to read correlated with refresh as caused by retention.
1. An Obligation Is a Rate, Not an Event
The structural fact everything here depends on.
CURRICULUM-DERIVED from 15.3: the controller accrues an obligation at a specified average interval, and it may postpone service up to a bounded number of intervals or pull in service ahead of time, also bounded. Chapter 15.3 owns the credit ledger that tracks the resulting position and its bounds.
So three different things can be true at once, and conflating them is where refresh debugging goes wrong:
| Statement | Meaning | Is it a failure? |
|---|---|---|
| A refresh is due | an obligation has accrued | no — this is normal |
| Service is urgent | the postponement allowance is nearly spent | no — this is the ledger working |
| An obligation is overdue | the allowance is spent and it is still unserved | yes |
| The oldest obligation exceeded its deadline | the device went unserved past its requirement | yes, and differently |
Rows three and four are not the same failure. Overdue is a statement about the controller's own ledger — it knows it is behind. A deadline miss is a statement about the device, and a controller can be overdue on its ledger while the device remains within specification if the ledger's bound is conservative.
That distinction is the first fork in the investigation, and it is measurable rather than a matter of judgement: compare the ledger's own state against the deadline arithmetic. §4 makes it a procedure.
2. Reading the Ledger as Evidence
Chapter 15.3 built the credit ledger. This chapter reads it, and the reading is not obvious because the ledger's healthy behaviour is to oscillate.
A ledger that never leaves zero is not a healthy ledger. It is a controller servicing eagerly at every opportunity, which 17.3's policy dial permits and which costs bandwidth. A ledger that swings across its full range and recovers is healthy. A ledger that swings to one bound and stays is the finding.
| Ledger behaviour | Supports | Does not establish |
|---|---|---|
| Oscillates, returns to centre | normal operation | that deadlines were met — §4 |
| Pinned at the postponement bound | service is starved — §10 | which resource is starving it |
| Pinned at the pull-in bound | service is over-eager | that bandwidth is the problem |
| Monotonically drifting | obligations accrue faster than they are retired | the cause of the imbalance |
| Flat at zero with traffic running | the ledger may not be accruing | anything about the device |
Row five is the dangerous one and it deserves the same suspicion 28.1 §4 gave an all-passing bitmap. A ledger reading zero with traffic running looks perfect. It is also exactly what a ledger that stopped accruing looks like — and a controller that never believes a refresh is due will never issue one.
The discriminating experiment is to stop issuing refresh and watch the ledger. If the ledger does not climb, it is not accruing, and every refresh decision downstream of it is being made on a constant. That is a control-path defect and no amount of retention analysis will find it.
3. Deadline Miss Versus Late Service
The first fork, as a procedure.
Four quantities localise it, and all four are available from telemetry:
| Quantity | What it tells you |
|---|---|
| Pending obligations | how many are outstanding right now |
| Oldest pending age | how long the oldest has waited |
| Maximum service delay | the worst age ever observed |
| Deadline misses | how many times the oldest exceeded its requirement |
DERIVED under a stated ILLUSTRATIVE model — average interval T, postponement allowance 8 intervals:
oldest_age (in intervals) ledger state device requirement
------------------------- ------------ ------------------
0 .. 3 due met
4 .. 7 urgent met
8 overdue AT the bound
9 and beyond overdue MISSED
so: overdue begins at 8 and a MISS begins at 9.
a controller reporting "overdue events: 412, deadline misses: 0"
is a controller using its allowance -- which is what the allowance
is for. That is a PERFORMANCE observation, not a correctness one.
a controller reporting "overdue: 412, misses: 3" has three
correctness events and 409 performance events, and the two need
completely different investigations.So the single most useful refresh telemetry field is the count of deadline misses, kept separately from the count of overdue events. A design that reports only overdue forces every performance excursion to be investigated as a correctness bug, and a design that reports only a combined refresh error count loses the distinction entirely.
And a miss count of zero does not clear refresh. It establishes that the obligation was met. §11 is about all the ways a refresh-adjacent failure survives that result.
4. A Deadline Miss, Cycle by Cycle
The ledger crossing its bound. ILLUSTRATIVE ages in interval units; 15.3 owns the verified allowance.
Overdue is a ledger state; a miss is a device statement
10 cyclesThe missed output stays high after the refresh is issued, and that is the design decision worth defending. A miss is a historical fact about the device, not a current condition. A flag that cleared when service resumed would make a system that missed a deadline every window look identical to one that never missed — and a boot log sampled at the wrong instant would read clean.
drain rising at interval 7 is 17.3's requirement, and it is also a debugging hazard. The manager must drain in-flight traffic before issuing refresh. So the interval during which drain is high is an interval in which normal traffic is deliberately stopped — and §9's latency spikes live exactly there. A latency excursion coincident with drain is the refresh manager working, not a fault.
And note what the trace does not contain: any statement about charge. The ages, the states and the miss are all controller bookkeeping. Whether the device actually lost data is a separate question that this trace cannot answer, and §11 is about why that gap matters.
5. The Three-Way Split as a Flow
The second message is the whole method: read the telemetry before forming a hypothesis. A refresh symptom invites an immediate retention story, and the four numbers that would refute it cost one register read.
And the third branch loops back to itself before reporting, because the drain interval is the cheapest thing to exclude and the most commonly mistaken for a fault.
6. Latency Spikes
The most common refresh complaint, and usually not a bug.
CURRICULUM-DERIVED from 17.3: the manager drains in-flight traffic, issues refresh, and re-enables normal traffic at an occupancy boundary. During the drain and the refresh, normal traffic is stopped. Chapter 15.5 owns the availability arithmetic that quantifies the cost.
So a periodic latency spike at the refresh interval is expected behaviour, and the debugging question is whether its shape is right.
| Observation | Supports | Discriminating experiment |
|---|---|---|
| Spike height ≈ drain + refresh duration | normal | none needed — this is the design |
| Spike much taller than that | the drain is not completing, or traffic resumes late | measure drain duration separately from refresh duration |
| Spikes at the wrong period | the interval is misconfigured, or the ledger is accruing wrongly | compare accrual rate against the configured interval |
| Spikes irregular | deferred policy bunching several refreshes | 17.3's policy dial — expected under deferral |
| Spike with no refresh issued | the drain ran and the refresh did not | drain high with ref_cmd low — a manager bug |
Row five is a real defect with a clean signature and it is worth naming. A manager that drains, then fails to issue, pays the entire cost of refresh and retires nothing. The ledger keeps climbing while the system pays for service it never received — and the symptom is latency spikes plus a drifting ledger, which §2 row four already flagged.
Row four is the one that gets misreported as a bug. Chapter 17.3 owns the eager-versus-deferred dial, and deferral deliberately bunches service to protect bursts. Irregular spikes under a deferred policy are the policy working, and the correct response is to check what the dial is set to before investigating anything.
7. Starvation and Non-Recovery
Two failure shapes that look similar and are not.
Starvation: refresh cannot get service. The ledger pins at the postponement bound, deadline misses begin, and the cause is upstream — some traffic class or arbitration policy is never yielding.
Non-recovery: traffic never resumes after refresh. The refresh completes, the ledger retires, and normal traffic does not restart.
| Starvation | Non-recovery | |
|---|---|---|
| Ledger | pinned at the bound, climbing | retires normally |
| Deadline misses | rising | zero |
| Traffic | running, refresh starved | stopped |
| Cause lives in | arbitration / policy — 17.4 | the occupancy boundary — 17.3 |
| First thing to read | what is winning arbitration | whether drain ever deasserts |
They are distinguished by one observation: is traffic running? Starvation is refresh losing to traffic; non-recovery is traffic never being let back in. The two have opposite fixes and identical top-level symptoms — "the system stalls around refresh".
Non-recovery has a specific failure mode worth stating. Chapter 17.3 owns an occupancy boundary at which normal traffic is re-enabled. If that boundary is never reached — because the condition depends on a queue that only fills when traffic runs — the manager deadlocks. The signature is drain permanently high with an empty queue, and it is a liveness bug rather than a refresh bug.
8. Who Owns the Obligation Right Now
Two mechanisms produce the same uncomfortable result: the controller's ledger is clean and part of the device went unserved. Both are about who owns the obligation, and neither appears anywhere in §3's four quantities.
Scope mismatch
Chapter 15.2 owns the mapping from command scope to controller bookkeeping, and its central point is that the two must match. A device-wide refresh retires one device-level obligation; a narrower-scope refresh retires one obligation for only the banks that command names, which requires one counter per bank.
So a mismatch between what the controller counts and what it issues is a silent starvation machine.
| Controller counts | Commands actually issued | Result |
|---|---|---|
| One device-level obligation | device-wide | correct |
| One device-level obligation | per-bank | one counter retired per command; most banks never served |
| Per-bank counters | device-wide | all counters retired per command — correct, if the device agrees |
| Per-bank counters | per-bank, wrong bank field | the named bank is served, a different counter is retired |
Row two is the dangerous one and its signature is exactly the signature of health. The controller accrues one obligation per interval and retires one per command, so pending oscillates correctly, oldest_age stays small, deadline_misses stays zero — and if the commands are per-bank, each one served a single bank. With 16 banks, 15 of them are being refreshed at one-sixteenth of the required rate.
No aggregate counter can detect this, which is the point. §3's four quantities are all device-level, and the failure is a distribution problem underneath them.
The discriminating evidence is per-resource, and 15.2 already built the instrument — its scope-aware service ledger tracks service per resource rather than in aggregate. The debugging instruction is to read that ledger's per-bank ages, not to build a new one, and the signature is unmistakable: one bank's age near zero and fifteen climbing.
The self-refresh handover
The second mechanism is a transfer rather than a mismatch. While the device is in self-refresh, the device refreshes itself and the controller's obligation is suspended. Entry and exit are therefore handover points, and a handover has two failure modes.
| Failure | Mechanism | Signature |
|---|---|---|
| Ledger not suspended on entry | the controller keeps ageing obligations it no longer owns | a burst of misses recorded during self-refresh, when nothing was wrong |
| Ledger not resumed on exit | accrual stays off after the device hands back | accruing_seen is true from before, so §3's dead-ledger check passes — and nothing accrues |
| Exit without re-establishing | settings that self-refresh invalidated are used as-is | corruption at the first accesses after exit — 28.4 |
Row two is the cruel one. Chapter 15.5's accounting and §3's accruing_seen bit both report healthy, because the ledger did accrue — before the sleep. A system that enters self-refresh once and never resumes accrual looks identical to a healthy one on every device-level field.
So the check is a rate, not a flag: obligations accrued per unit of non-self-refresh time. A ledger that has accrued nothing since the last exit, while traffic runs, is the finding — and it is the same shape as §2 row five, one level down.
// SIMULATION-ONLY. Shows why aggregate refresh telemetry cannot see a
// scope mismatch, by modelling per-bank service against a device-level
// ledger. It does NOT rebuild 15.2's scope-aware ledger -- it consumes
// the idea and demonstrates the blind spot.
//
// WHAT IT DOES NOT MODEL: the device, charge, or the command encoding.
// WHAT IT WOULD MISS: any starvation that is uniform across banks,
// which an aggregate counter WOULD see -- this model is only
// sensitive to the distribution.
class scope_mismatch_demo #(parameter int BANKS = 16);
int bank_age[BANKS]; // per-bank intervals since service
int device_pending; // the aggregate ledger §11 publishes
int device_misses;
function void new_interval();
device_pending++;
foreach (bank_age[b]) bank_age[b]++;
endfunction
// A device-wide command serves every bank and retires one aggregate
// obligation. Both views agree.
function void refresh_all_banks();
if (device_pending > 0) device_pending--;
foreach (bank_age[b]) bank_age[b] = 0;
endfunction
// A per-bank command serves ONE bank -- and a controller keeping a
// single device-level counter retires the same one obligation it
// would have retired for a device-wide command. That is the bug.
function void refresh_one_bank(int b);
if (device_pending > 0) device_pending--;
if (b >= 0 && b < BANKS) bank_age[b] = 0;
endfunction
function int worst_bank_age();
int w = 0;
foreach (bank_age[b]) if (bank_age[b] > w) w = bank_age[b];
return w;
endfunction
// The whole point: the aggregate view is clean and the distribution
// is not. A test asserts these DISAGREE under a scope mismatch.
function bit aggregate_looks_healthy(int allowance);
return device_pending <= allowance;
endfunction
function bit distribution_is_starved(int deadline);
return worst_bank_age() >= deadline;
endfunction
endclassThe class exists to make one assertion possible: aggregate_looks_healthy() && distribution_is_starved(). That conjunction is impossible under matched scope and routine under a mismatch — and it is the only way to express a failure that every device-level number reports as health.
9. The Trap — Corruption That Correlates With Refresh
The third question, and the one this chapter exists to slow down.
Observation: data errors cluster near refresh activity. The instinct is retention: charge decayed, refresh came too late, data was lost.
That is one hypothesis out of at least four, and it is not the cheapest to test.
| Hypothesis | Mechanism | Evidence that would support it | Evidence that would refute it |
|---|---|---|---|
| A. Retention | the cell genuinely lost charge before service | errors grow with oldest-age; worse at higher temperature; deadline misses non-zero | deadline misses are zero and errors do not track age |
| B. Drain / turnaround | the traffic stopped and restarted around refresh, and the restart is wrong | errors are at the first access after drain falls, regardless of age | errors are uniformly spread through the window |
| C. Timing interaction | a rule involving refresh is violated — 28.2 | a timing violation snapshot with a refresh-adjacent rule | the snapshot is empty |
| D. Thermal coincidence | refresh bursts raise local temperature, and something else is temperature-sensitive | errors track temperature, not refresh count | temperature held constant and errors persist |
Hypothesis B is the one most often missed and it is usually cheapest to test. The refresh window is a traffic stop and restart. A bug in the restart — a stale pointer, a queue resumed in the wrong state, a turnaround timing case that only occurs after an idle gap — produces errors at the first access after every refresh, with no retention involvement at all.
And the discriminating observation is beautifully simple: where in the window are the errors?
bucket the failing accesses by their position in the refresh window
(B) drain / restart (A) retention
------------------ --------------
|# | | #|
|# | | ##|
|## | | ####|
+------------------+ +-----------------+
^ first access age -> ^ oldest
after drain falls cells
B clusters at the START of the window, immediately after traffic
resumes, and does not care how old the obligation was.
A clusters at the END, and gets worse as oldest_age grows.
the same top-level symptom -- "corruption correlated with refresh"
-- produces opposite distributions, and one histogram separates
them.10. What Raising the Refresh Rate Actually Proves
The standard experiment, and the standard over-reading.
Raising the refresh rate changes at least four things simultaneously:
| Changed | Consequence |
|---|---|
| Retention margin | cells are served sooner — the intended variable |
| Bus occupancy | less bandwidth for traffic — 15.5 |
| Drain frequency | more traffic stops and restarts per unit time — hypothesis B's exposure rises |
| Thermal profile | more refresh activity, different local heating |
So "the failure went away when I doubled the refresh rate" supports hypothesis A — and it is also consistent with the others in ways that are easy to miss.
The counter-intuitive case is hypothesis B. More refresh means more drain-and-restart events, so a restart bug should get worse, not better. If the failure improves, that argues against B — which makes the experiment genuinely informative, just not in the direction people usually claim.
And there is a confound that makes the result weaker than it looks. Raising the refresh rate reduces available bandwidth, which reduces the traffic rate — and a failure whose probability scales with traffic will improve for that reason alone, with no refresh involvement at all.
| To make the experiment discriminating | Hold this constant |
|---|---|
| Separate retention from traffic rate | throttle traffic to the same delivered bandwidth in both runs |
| Separate retention from thermal | hold case temperature and allow soak time |
| Separate retention from restart count | compare errors per drain event, not per unit time |
The third row is the one that turns the experiment from suggestive into decisive. Errors per refresh window rather than errors per second removes the exposure-rate confound entirely: if doubling the refresh rate halves errors per second but leaves errors per window unchanged, the refresh rate changed the exposure and not the mechanism.
11. Temperature Band Changes
Chapter 15.4 owns the band-change problem that invalidates prior accounting, and it produces a debugging signature worth recognising on sight.
The refresh requirement is specified against an operating condition, and crossing into a different temperature band changes the requirement. So a system that was correct becomes incorrect without any configuration change — the configuration stopped matching the conditions.
| Signature | Supports |
|---|---|
| Misses begin after sustained load, never at idle | the band changed under thermal load and the rate did not follow |
| Misses correlate with ambient temperature | same |
| Misses begin immediately at a fixed rate | a static misconfiguration, not a band change |
| The rate does change with temperature, and misses still occur | the band detection or its threshold is wrong |
The distinguishing question is whether the refresh rate tracked the condition. Reading the configured interval alongside the temperature reading answers it, and it is a two-register check.
This is also the clearest example in the module of the second law. A miss reported at minute 40 of a soak test is not a minute-40 event: the causal error is the band crossing that happened earlier and was not acted on, and the telemetry that would show it is the interval-versus-temperature history, not the miss itself.
12. Refresh Telemetry
// ---------------------------------------------------------------------
// refresh_telemetry -- CLASSIFICATION: synthesisable, BINDABLE.
//
// WHAT IT DOES: keeps §3's four quantities, separates OVERDUE events
// from DEADLINE MISSES (§3 -- they route to different
// investigations), and buckets data errors by position in the refresh
// window so §9's two opposite distributions are distinguishable.
//
// WHY OVERDUE AND MISS ARE SEPARATE COUNTERS: 15.3 owns a postponement
// allowance, and USING it is not a fault. A design reporting one
// combined "refresh error" count forces every performance excursion to
// be investigated as a correctness bug.
//
// WHY THE MISS FLAG IS STICKY (§4): a miss is a historical fact about
// the device, not a current condition. A flag that cleared when
// service resumed would make a system missing every window look
// identical to one that never missed.
//
// WHY THE HISTOGRAM (§9): retention clusters errors at the END of the
// window and grows with age; a drain/restart bug clusters them at the
// START and does not care about age. One histogram separates the two
// hypotheses that a "correlated with refresh" symptom cannot.
//
// WHAT IT CANNOT TELL YOU: whether charge was lost. Every field is
// controller bookkeeping. 15.4 owns retention as a SPECIFICATION and
// nothing here measures a physical quantity.
//
// SYNTHESIS: counters, one small histogram array, no DUT handle.
// ---------------------------------------------------------------------
module refresh_telemetry #(
parameter int MAX_PENDING = 16, // postponement allowance, ILLUSTRATIVE
parameter int DEADLINE_AGE = 9, // ages beyond this are MISSES -- §3
parameter int BUCKETS = 8, // window-position histogram -- §9
parameter int AGE_W = 12
)(
input logic clk,
input logic rst_n,
// ---- refresh manager interface (observed only)
input logic accrue, // an obligation accrues
input logic ref_issued, // a refresh retires one
input logic drain, // 17.3's drain window
input logic temp_band_changed, // §11
// ---- data-path error strobe (§9's histogram input)
input logic err_valid,
input logic clear,
// ---- published evidence
output logic [$clog2(MAX_PENDING+1)-1:0] pending,
output logic [AGE_W-1:0] oldest_age,
output logic [AGE_W-1:0] max_delay,
output logic [15:0] overdue_events,
output logic [15:0] deadline_misses,
output logic miss_sticky,
output logic accruing_seen,
output logic [15:0] drains,
output logic [15:0] drains_without_issue,
output logic [15:0] band_changes,
output logic [15:0] err_bucket [BUCKETS]
);
// $clog2(1) is 0, which makes the pending vector illegal as [-1:0].
initial begin
if (MAX_PENDING < 2) $fatal(1, "refresh_telemetry: MAX_PENDING must be >= 2 (got %0d)", MAX_PENDING);
if (BUCKETS < 2) $fatal(1, "refresh_telemetry: BUCKETS must be >= 2");
if (AGE_W < 4) $fatal(1, "refresh_telemetry: AGE_W must be >= 4");
// A deadline at or below the allowance would make every use of the
// allowance a miss, collapsing §3's distinction the block exists
// to preserve.
if (DEADLINE_AGE < 2) $fatal(1, "refresh_telemetry: DEADLINE_AGE must be >= 2");
end
localparam logic [AGE_W-1:0] AGE_MAX = {AGE_W{1'b1}};
logic drain_q;
logic in_window; // since drain fell
logic [AGE_W-1:0] since_drain;
logic issued_this_drain;
always_ff @(posedge clk) begin
if (!rst_n || clear) begin
pending <= '0;
oldest_age <= '0;
max_delay <= '0;
overdue_events <= '0;
deadline_misses <= '0;
miss_sticky <= 1'b0;
accruing_seen <= 1'b0;
drains <= '0;
drains_without_issue <= '0;
band_changes <= '0;
drain_q <= 1'b0;
in_window <= 1'b0;
since_drain <= '0;
issued_this_drain <= 1'b0;
for (int b = 0; b < BUCKETS; b++) err_bucket[b] <= '0;
end else begin
drain_q <= drain;
// §2 row five: a ledger that never accrues looks identical to a
// healthy one at zero. Publishing "have we EVER seen an accrual"
// makes a dead accrual path a one-bit check.
if (accrue) accruing_seen <= 1'b1;
// ---- pending, saturating at the allowance rather than wrapping
if (accrue && !ref_issued && pending != MAX_PENDING[$clog2(MAX_PENDING+1)-1:0])
pending <= pending + 1'b1;
else if (ref_issued && !accrue && pending != '0)
pending <= pending - 1'b1;
// ---- oldest age. Ages while anything is pending; resets only
// when the LAST pending obligation retires, because the age is
// the oldest one's, not the most recent one's.
if (ref_issued && pending <= 1) oldest_age <= '0;
else if (pending != '0 && oldest_age != AGE_MAX)
oldest_age <= oldest_age + 1'b1;
if (oldest_age > max_delay) max_delay <= oldest_age;
// ---- §3's two DIFFERENT counters. Overdue is a ledger state;
// a miss is a device statement. Counting them together would
// make 412 performance events and 3 correctness events into one
// meaningless number.
if (pending == MAX_PENDING[$clog2(MAX_PENDING+1)-1:0]
&& overdue_events != 16'hFFFF)
overdue_events <= overdue_events + 1'b1;
if (oldest_age >= DEADLINE_AGE[AGE_W-1:0]) begin
if (deadline_misses != 16'hFFFF) deadline_misses <= deadline_misses + 1'b1;
// §4: sticky. Issuing a refresh afterwards does not undo it.
miss_sticky <= 1'b1;
end
// ---- §6 row five: a drain that runs without issuing pays the
// whole cost and retires nothing.
if (drain && !drain_q) begin
if (drains != 16'hFFFF) drains <= drains + 1'b1;
issued_this_drain <= 1'b0;
end
if (drain && ref_issued) issued_this_drain <= 1'b1;
if (!drain && drain_q) begin
if (!issued_this_drain && drains_without_issue != 16'hFFFF)
drains_without_issue <= drains_without_issue + 1'b1;
// window position restarts when traffic resumes
in_window <= 1'b1;
since_drain <= '0;
end
if (in_window && since_drain != AGE_MAX) since_drain <= since_drain + 1'b1;
// ---- §9's histogram: bucket errors by position since traffic
// resumed. Bucket 0 is the first access after drain fell.
if (err_valid && in_window) begin
automatic int b = (since_drain >= BUCKETS) ? BUCKETS - 1 : int'(since_drain);
if (err_bucket[b] != 16'hFFFF) err_bucket[b] <= err_bucket[b] + 1'b1;
end
if (temp_band_changed && band_changes != 16'hFFFF)
band_changes <= band_changes + 1'b1;
end
end
endmoduleoverdue_events and deadline_misses are two counters, and that is §3's argument in hardware. Using the postponement allowance is what the allowance is for. A single combined counter would make 412 performance events and 3 correctness events indistinguishable — and the 3 are the ones that matter.
accruing_seen is one bit that answers §2's most dangerous signature. A ledger reading zero is either healthy or dead, and a design with no accrual path reports a perfect refresh record forever. One sticky bit separates them.
drains_without_issue catches §6 row five. A manager that drains and fails to issue pays the full bandwidth cost of refresh and retires nothing, and the ledger climbs while the system appears to be doing refresh work. The counter makes it a query rather than a waveform hunt.
13. Ranking the Correlation Hypotheses
// ---------------------------------------------------------------------
// refresh_correlation_ranker -- SIMULATION-ONLY.
//
// Ranks §9's four hypotheses from §12's telemetry. It returns a LIST
// and an experiment, never a cause -- and it refuses to return a
// retention verdict at all, because nothing digital measures charge.
//
// WHAT IT DOES NOT MODEL: charge, temperature physics, the device.
//
// WHAT IT WOULD MISS: a mechanism whose window-position distribution
// matches another's. The histogram separates START-clustered from
// END-clustered; it does not separate all four hypotheses.
// ---------------------------------------------------------------------
typedef enum {
HYP_RETENTION, HYP_DRAIN_RESTART, HYP_TIMING, HYP_THERMAL
} refresh_hyp_e;
class refresh_snapshot;
int pending;
int oldest_age;
int max_delay;
int overdue_events;
int deadline_misses;
bit miss_sticky;
bit accruing_seen;
int drains;
int drains_without_issue;
int band_changes;
int err_bucket[]; // §9's histogram
bit timing_snapshot_valid; // from 28.2's snapshot
bit temperature_controlled; // was the run thermally held?
endclass
class refresh_correlation_ranker;
// §9: START-clustered means the first buckets dominate; END-clustered
// means the last do. Returns -1 when the histogram is empty, so "no
// errors" is distinct from "uniformly spread".
function int peak_bucket(refresh_snapshot s);
int best = -1, bestv = 0;
foreach (s.err_bucket[i])
if (s.err_bucket[i] > bestv) begin bestv = s.err_bucket[i]; best = i; end
return best;
endfunction
function int total_errors(refresh_snapshot s);
int n = 0;
foreach (s.err_bucket[i]) n += s.err_bucket[i];
return n;
endfunction
// §2 row five, checked FIRST: a ledger that never accrued makes every
// other refresh number meaningless, so no hypothesis is ranked until
// it is excluded.
function bit telemetry_trustworthy(refresh_snapshot s);
return s.accruing_seen;
endfunction
function void rank(refresh_snapshot s, ref refresh_hyp_e out[$]);
int pk = peak_bucket(s);
int n = s.err_bucket.size();
out.delete();
if (!telemetry_trustworthy(s)) return; // §2: nothing is rankable
// §6 row five and §7: a manager that drains without issuing is a
// control defect, and its errors are restart errors.
if (s.drains_without_issue > 0) out.push_back(HYP_DRAIN_RESTART);
// §9: START-clustered -> drain/restart. Checked before retention
// because it is cheaper to test and is the commonly missed one.
if (pk >= 0 && pk < (n / 4)) out.push_back(HYP_DRAIN_RESTART);
// 28.2's snapshot is non-empty: a refresh-adjacent timing rule
// fired, and that is a digital fact rather than an inference.
if (s.timing_snapshot_valid) out.push_back(HYP_TIMING);
// §11: a band change with misses points at the rate not following
// the condition.
if (s.band_changes > 0 && !s.temperature_controlled)
out.push_back(HYP_THERMAL);
// §9 hypothesis A is offered ONLY when the obligation was actually
// missed AND the errors cluster at the END of the window. Without
// a miss, the device was served within its requirement and a
// retention story has no supporting evidence.
if (s.deadline_misses > 0 && pk >= (3 * n / 4))
out.push_back(HYP_RETENTION);
endfunction
// §10's confound, made a method so a caller cannot forget it. Errors
// per DRAIN EVENT removes the exposure-rate confound that errors per
// second does not.
function real errors_per_window(refresh_snapshot s);
if (s.drains == 0) return -1.0; // not "zero errors"
return real'(total_errors(s)) / real'(s.drains);
endfunction
function string next_experiment(refresh_snapshot s);
int pk = peak_bucket(s);
int n = s.err_bucket.size();
if (!telemetry_trustworthy(s))
return "the ledger never accrued: stop issuing refresh and confirm it climbs before anything else is investigated";
if (s.drains_without_issue > 0)
return "drains without an issue are non-zero: the manager pays for refresh and retires nothing -- read 17.3's issue path before any retention work";
if (pk >= 0 && pk < (n / 4))
return "errors cluster at the first accesses after drain: compare errors per drain event across two refresh rates; unchanged per-window => restart bug, not retention";
if (s.deadline_misses == 0)
return "no deadline was missed: the obligation was met, so a retention story has no supporting evidence -- bucket the errors and check the drain interval";
if (pk >= (3 * n / 4))
return "errors cluster at the end of the window with misses present: hold temperature and bandwidth constant, then compare errors per window across refresh rates";
return "no distinguishing distribution: collect more windows before changing any configuration";
endfunction
// §9's boundary, enforced in code rather than in a comment.
function string retention_verdict(refresh_snapshot s);
return "unavailable: retention is a specified physical property (15.4). No controller telemetry measures charge, and a correlation with refresh position is consistent with at least four mechanisms (§9)";
endfunction
endclassrank() returns an empty list when the ledger never accrued, and that ordering is §2's argument made structural: if the telemetry is not trustworthy, no hypothesis about the device is rankable at all.
Retention is offered only when a deadline was actually missed and the errors cluster at the end of the window. Without a miss the obligation was met, and a retention story then has no supporting evidence — so the class declines to rank it rather than listing it last.
And errors_per_window() returns −1 when there were no drains. Zero errors over zero windows is not a rate, and returning 0.0 would let a run that never refreshed look like a clean one — the same three-valued discipline this curriculum has now needed in 27.3 §6, 27.4 §2, 28.1 §17 and 28.2 §16.
14. What the Assertions Prove
// Bound to §12's telemetry. Every property carries `disable iff`, and
// every antecedent is covered below -- 27.2 measured this curriculum
// at 78.53% implications, so a silent pass is otherwise
// indistinguishable from an unbound block.
module refresh_telemetry_sva #(
parameter int MAX_PENDING = 16,
parameter int DEADLINE_AGE = 9,
parameter int AGE_W = 12
)(
input logic clk, rst_n, clear,
input logic accrue, ref_issued, drain, err_valid, temp_band_changed,
input logic [$clog2(MAX_PENDING+1)-1:0] pending,
input logic [AGE_W-1:0] oldest_age, max_delay,
input logic [15:0] overdue_events, deadline_misses, drains, drains_without_issue,
input logic miss_sticky, accruing_seen
);
// ---- P1. FORBIDDEN. A miss flag never clears without an explicit
// clear. §4: a miss is a historical fact, and a flag that cleared on
// service would make a system missing every window look clean.
property p_miss_sticky;
@(posedge clk) disable iff (!rst_n)
(miss_sticky && !clear) |=> miss_sticky;
endproperty
assert property (p_miss_sticky)
else $error("telemetry: miss_sticky cleared without a clear");
// ---- P2. FORBIDDEN. Issuing a refresh does not clear the miss.
// This is P1's specific and most tempting violation.
property p_issue_does_not_clear_miss;
@(posedge clk) disable iff (!rst_n)
(miss_sticky && ref_issued && !clear) |=> miss_sticky;
endproperty
assert property (p_issue_does_not_clear_miss)
else $error("telemetry: a refresh issue cleared the miss flag");
// ---- P3. A miss is recorded whenever the oldest age reaches the
// deadline. Without this the flag could be sticky and never set.
property p_miss_recorded;
@(posedge clk) disable iff (!rst_n)
(oldest_age >= DEADLINE_AGE) |=> miss_sticky;
endproperty
assert property (p_miss_recorded)
else $error("telemetry: deadline age reached without recording a miss");
// ---- P4. FORBIDDEN. Overdue and miss are DIFFERENT counters. An
// overdue event must not increment the miss count. §3: 412
// performance events and 3 correctness events must stay separable.
property p_overdue_is_not_a_miss;
@(posedge clk) disable iff (!rst_n)
(pending == MAX_PENDING && oldest_age < DEADLINE_AGE)
|=> (deadline_misses == $past(deadline_misses));
endproperty
assert property (p_overdue_is_not_a_miss)
else $error("telemetry: an overdue event incremented the miss count");
// ---- P5. INVARIANT. pending never exceeds the allowance. The
// counter is sized for MAX_PENDING+1 values -- COUNT versus INDEX --
// precisely so this cannot wrap silently.
property p_pending_bounded;
@(posedge clk) disable iff (!rst_n)
(pending <= MAX_PENDING);
endproperty
assert property (p_pending_bounded)
else $error("telemetry: pending %0d exceeded the allowance", pending);
// ---- P6. max_delay is monotone within a run. A high-water mark
// that fell would erase the worst case it exists to remember.
property p_max_delay_monotone;
@(posedge clk) disable iff (!rst_n)
(!clear) |=> (max_delay >= $past(max_delay));
endproperty
assert property (p_max_delay_monotone)
else $error("telemetry: max_delay decreased");
// ---- P7. max_delay bounds the current age at all times.
property p_max_delay_bounds_age;
@(posedge clk) disable iff (!rst_n)
(max_delay >= oldest_age) || (oldest_age == 0);
endproperty
assert property (p_max_delay_bounds_age)
else $error("telemetry: oldest_age %0d exceeds max_delay %0d", oldest_age, max_delay);
// ---- P8. FORBIDDEN. The age never advances with nothing pending.
// An age that ticked on an empty ledger would manufacture misses.
property p_no_age_without_pending;
@(posedge clk) disable iff (!rst_n)
(pending == '0) |=> (oldest_age <= $past(oldest_age));
endproperty
assert property (p_no_age_without_pending)
else $error("telemetry: oldest_age advanced with nothing pending");
// ---- P9. accruing_seen is sticky. §2 row five: it is the one bit
// that separates a healthy ledger at zero from a dead one.
property p_accruing_seen_sticky;
@(posedge clk) disable iff (!rst_n)
(accruing_seen && !clear) |=> accruing_seen;
endproperty
assert property (p_accruing_seen_sticky)
else $error("telemetry: accruing_seen dropped");
// ---- P10. An accrual sets it. Together with P9 this makes the bit
// a faithful "has the ledger ever moved".
property p_accrue_sets_seen;
@(posedge clk) disable iff (!rst_n)
accrue |=> accruing_seen;
endproperty
assert property (p_accrue_sets_seen)
else $error("telemetry: an accrual did not set accruing_seen");
// ---- P11. A drain that ends without an issue is counted. §6 row
// five: the manager paid the whole cost and retired nothing.
property p_drain_without_issue_counted;
@(posedge clk) disable iff (!rst_n)
($fell(drain) && !$past(ref_issued) && drains_without_issue != 16'hFFFF)
|-> (drains_without_issue >= $past(drains_without_issue));
endproperty
assert property (p_drain_without_issue_counted)
else $error("telemetry: a drain without an issue was not counted");
// ---- P12. INVARIANT. Counters saturate rather than wrap. A wrapped
// miss count reading 2 after 65,538 misses would understate a
// systemic failure catastrophically.
property p_misses_saturate;
@(posedge clk) disable iff (!rst_n)
(deadline_misses == 16'hFFFF) |=> (deadline_misses == 16'hFFFF);
endproperty
assert property (p_misses_saturate)
else $error("telemetry: deadline_misses wrapped past saturation");
// ---- 27.2 §7: publish every antecedent.
cover property (@(posedge clk) disable iff (!rst_n) accrue);
cover property (@(posedge clk) disable iff (!rst_n) ref_issued);
cover property (@(posedge clk) disable iff (!rst_n) drain);
cover property (@(posedge clk) disable iff (!rst_n) $fell(drain));
cover property (@(posedge clk) disable iff (!rst_n) err_valid);
cover property (@(posedge clk) disable iff (!rst_n) clear);
cover property (@(posedge clk) disable iff (!rst_n) temp_band_changed);
cover property (@(posedge clk) disable iff (!rst_n) pending == MAX_PENDING);
cover property (@(posedge clk) disable iff (!rst_n) miss_sticky);
// P3's antecedent, published explicitly: without this cover, a run
// that never reached the deadline age is indistinguishable from one
// where P3 armed and held -- which is 27.2 §2's vacuity argument
// applied to this property specifically.
cover property (@(posedge clk) disable iff (!rst_n)
oldest_age >= DEADLINE_AGE);
cover property (@(posedge clk) disable iff (!rst_n) !accruing_seen);
cover property (@(posedge clk) disable iff (!rst_n) drains_without_issue != 16'd0);
cover property (@(posedge clk) disable iff (!rst_n) overdue_events != 16'd0 && deadline_misses == 16'd0);
cover property (@(posedge clk) disable iff (!rst_n) err_valid && drain);
endmodule15. DV — Testing the Telemetry
// SIMULATION-ONLY. Independent reference: it replays the accrual and
// issue stream into a QUEUE of obligation timestamps and derives the
// age by inspecting the queue head, rather than maintaining an
// incremental age register. A different algorithm, so agreement is
// evidence -- 27.4 §15's argument.
class refresh_reference;
int obligations[$]; // accrual cycle of each pending obligation
int now;
int max_delay_seen;
int misses;
int overdue;
function void tick();
now++;
if (obligations.size() > 0) begin
int age = now - obligations[0];
if (age > max_delay_seen) max_delay_seen = age;
end
endfunction
function void accrue();
obligations.push_back(now);
endfunction
// A refresh retires the OLDEST obligation, which is what makes the
// queue head the right place to read the age from.
function void issue();
if (obligations.size() > 0) void'(obligations.pop_front());
endfunction
function int oldest_age();
if (obligations.size() == 0) return 0;
return now - obligations[0];
endfunction
// §3: two different classifications from one age.
function void classify(int allowance, int deadline);
int a = oldest_age();
if (obligations.size() >= allowance) overdue++;
if (a >= deadline) misses++;
endfunction
function int pending(); return obligations.size(); endfunction
endclass| Check | What it establishes |
|---|---|
Replay §4's trace; compare pending, oldest_age, max_delay | Queue-based and register-based models agree |
| 100,000 random accrue/issue streams through both | Two algorithms agree on all four §3 quantities |
Accrue 16 with no issue (MAX_PENDING = 16) | pending saturates; P5 |
Hold oldest_age at DEADLINE_AGE − 1 | overdue_events rises, deadline_misses stays 0 — P4 |
Push oldest_age to DEADLINE_AGE | miss_sticky sets — P3 |
| Issue a refresh after a miss | miss_sticky stays set — P2 |
Assert clear | Flag and counters release |
Run with accrue tied low | accruing_seen stays 0; ranker returns an empty hypothesis list |
| Drain with no issue | drains_without_issue rises — P11 |
| Errors only at the first access after drain falls | Histogram peaks at bucket 0; ranker returns drain/restart first |
| Errors only at the end of the window, misses non-zero | Ranker offers retention last |
| Errors at the end of the window, misses zero | Ranker does not offer retention at all |
errors_per_window() with zero drains | Returns −1.0, not 0.0 |
| 70,000 misses | deadline_misses saturates — P12 |
| Run with the manager disconnected | All 12 properties pass; all 13 covers empty |
The two runs worth publishing are the ones that look like a clean refresh result and are not:
TWO PASSING REFRESH REPORTS
(A) the ledger never accrued
accrue tied low; traffic running; no refresh ever issued
pending 0
oldest_age 0
max_delay 0
overdue_events 0
deadline_misses 0
miss_sticky 0
accruing_seen 0 <-- the only non-clean field
------------------------------------------------
all 12 properties PASS
cover accrue 0 hits
cover ref_issued 0 hits
cover pending == MAX 0 hits
every refresh number is perfect and no refresh ever happened.
A report reading "refresh: 0 misses, 0 overdue" is produced by
this run AND by a flawless one, and the ONE field that
separates them is a sticky bit nobody usually publishes.
§13's ranker returns an EMPTY hypothesis list here, which is
the correct behaviour: with untrustworthy telemetry, nothing
about the device is rankable.
(B) the obligation was met and data was still corrupted
stimulus : full traffic, refresh healthy, errors observed
deadline_misses 0
overdue_events 91
max_delay 6 (deadline 9)
drains 4,118
drains_without_issue 0
err_bucket[0] 2,204 <-- first access after drain
err_bucket[1..6] 0
err_bucket[7] 3
------------------------------------------------
all 12 properties PASS
all 13 covers HIT
the refresh obligation was met with three intervals of margin
and 2,207 data errors occurred. 99.9% of them are at the FIRST
access after traffic resumed.
§9's histogram says drain/restart, not retention -- and the
retention story, which is what "corruption correlated with
refresh" usually becomes, is not merely unsupported here: the
obligation was never missed, so it has no evidence at all.
diagnosis : (A) is the vacuity case with a refresh face. (B) is
§9's trap: the correlation is real, the mechanism is a traffic
restart, and raising the refresh rate -- §10 -- would have made
it WORSE while appearing to change something.
the fix : (A) publish accruing_seen and read it first. (B) bucket
the errors by window position before proposing any mechanism,
and compare errors PER WINDOW across rates rather than per
second.16. Corner Cases
| Case | Behaviour | Why |
|---|---|---|
MAX_PENDING = 1 | Elaboration fails | $clog2(1) is 0; the pending vector would be [-1:0] |
DEADLINE_AGE < 2 | Elaboration fails | every use of the allowance would become a miss |
| Ledger at zero, traffic running | accruing_seen distinguishes healthy from dead | §2 row five |
| Overdue but not past the deadline | Overdue counts; misses do not | §3, P4 |
| Refresh issued after a miss | miss_sticky stays set | §4, P2 |
| Age advancing with nothing pending | P8 fires | it would manufacture misses |
| Drain with no issue | drains_without_issue rises | §6 row five |
| Drain never deasserts | Non-recovery, not starvation | §7 — read whether traffic is running |
| Errors peaked at bucket 0 | Drain/restart ranked first | §9 |
| Errors at the end, zero misses | Retention not offered | §13 — no supporting evidence |
| Zero drains | errors_per_window() returns −1.0 | not a rate; 0.0 would read as clean |
| Temperature band change with misses | Thermal hypothesis offered | §11 |
| 70,000 misses | Count saturates | P12 — honest, imprecise |
| Refresh rate doubled, errors per second halved | Nothing proved | §10 — traffic rate fell too |
| Refresh rate doubled, errors per window unchanged | Exposure changed, mechanism did not | §10's decisive form |
Rows fourteen and fifteen are the pair this chapter exists for. The same experiment supports a retention conclusion or refutes it depending on which denominator you use, and per-second is the one people report.
17. Misconceptions
“A refresh is due, so something is wrong.” §1. Due is normal, urgent is the ledger working, overdue is a controller state, and only a deadline miss is a statement about the device.
“Overdue means a deadline was missed.” §3. Overdue is the postponement allowance being spent — which is what it is for. 412 overdue events with zero misses is a performance observation.
“Zero misses means refresh is fine.” §2, report (A). A ledger that never accrues reports zero forever. Publish whether it has ever moved.
“Corruption correlated with refresh is a retention problem.” §9. Four hypotheses, and a drain/restart bug is usually cheaper to test and clusters the errors at the opposite end of the window.
“Doubling the refresh rate fixed it.” §10. It changed retention margin, bandwidth, drain frequency and thermal profile at once — and reduced the traffic rate, which alone can improve a traffic-proportional failure.
“More refresh can only help.” §10. It increases the number of drain-and-restart events, so a restart bug gets more exposure, not less.
“Latency spikes at refresh are a bug.” §6. The manager stops traffic to drain. The question is whether the spike's height and period match the design, not whether it exists.
“Irregular refresh spacing is a scheduling bug.” §6 row four. Chapter 17.3's policy dial deliberately defers and bunches service. Read the dial before investigating.
“The system stalls around refresh, so refresh is starved.” §7. Starvation and non-recovery have identical top-level symptoms and opposite causes; whether traffic is running separates them.
“A miss flag should clear when service resumes.” §4, P2. Then a system missing every window looks identical to one that never missed.
“The telemetry proves the device retained its data.” §14's callout. Every field is controller bookkeeping. Nothing here measures charge.
18. Interview Reasoning
What is a refresh obligation? A rate, not an event. The device requires a certain amount of service over a window, and the controller may postpone or pull in within a bounded allowance.
Distinguish overdue from a deadline miss. Overdue is the controller's own ledger saying the allowance is spent. A miss is the device going unserved past its requirement. A design that counts them together makes correctness events invisible among performance events.
Refresh telemetry reads zero misses and zero overdue. Are you satisfied? Not until I know the ledger ever accrued. A dead accrual path produces exactly those numbers and never issues a refresh.
Data errors correlate with refresh. What do you do first? Bucket the failing accesses by their position in the refresh window. Retention clusters them at the end and grows with age; a drain/restart bug clusters them at the first access after traffic resumes.
Why is that histogram decisive? Because the two mechanisms produce opposite distributions from the same top-level symptom, and it costs one counter array rather than a thermal chamber.
You double the refresh rate and errors halve. What have you proved? Very little. You changed retention margin, bandwidth, drain count and thermal profile, and you reduced the traffic rate. Compare errors per refresh window instead.
What would make that experiment decisive? Holding delivered bandwidth and case temperature constant, and comparing errors per drain event. If per-window error count is unchanged, the rate changed exposure, not mechanism.
The system stalls around refresh. Starvation or non-recovery? Ask whether traffic is running. Starvation is refresh losing arbitration with traffic flowing; non-recovery is traffic never being re-enabled after the drain.
Misses begin 40 minutes into a soak test. Hypothesis? A temperature band change that the refresh rate did not follow. The causal event is the crossing, not the miss — read the configured interval alongside the temperature history.
Can you conclude retention from digital evidence? No. You can conclude that errors occurred, that they correlated with window position and with age, and that deadlines were missed. Retention is a specified physical property and nothing in a controller measures charge.
19. Exercises
-
§3 places the miss boundary one interval past the allowance. Derive what changes if the deadline equals the allowance, and say which of §12's counters becomes useless.
-
§9 gives two histogram shapes. Construct a third mechanism whose shape matches neither, and say what additional instrumentation would separate it.
-
§10 lists four variables that change together. Design the smallest set of runs that isolates each, and state the total lab time at one hour per run.
-
§12 makes the miss flag sticky and the overdue count free-running. Argue for making the overdue count sticky as well, then decide.
-
§13's ranker refuses retention when misses are zero. Construct the case where that refusal is wrong, and say what evidence would have to be added for the ranker to handle it.
-
§7 distinguishes starvation from non-recovery. Write the single telemetry field you would add to make the distinction automatic, and say why it is not already there.
-
§11's band-change signature is "misses after sustained load". Design the test that distinguishes it from a static misconfiguration in one run rather than two.
-
A colleague reports that disabling refresh entirely makes a corruption failure disappear. Identify every hypothesis that result is consistent with, and say what it proves about retention.
20. Where This Goes
A refresh problem is three investigations wearing one name. An obligation is a rate with a bounded allowance, so due and urgent and overdue are not failures and only a deadline miss is a statement about the device; a ledger reading zero is either healthy or dead and one sticky bit separates them; and corruption that correlates with refresh has at least four mechanisms whose window-position distributions differ.
Four results carry forward. Overdue and miss are different counters, and merging them buries three correctness events under four hundred performance ones. A miss flag must be sticky, because service resuming does not undo a deadline that passed. The window-position histogram separates a restart bug from retention at the cost of one counter array. And errors per window, not per second, is what makes a refresh-rate experiment decisive rather than suggestive.
Two things stay open. Nothing here measures charge, so the strongest available statement is a correlation plus a miss count — and §13 encodes that as a refusal rather than a caveat. And the deadline itself arrives as a parameter through 28.2 §11's provenance chain, so every property in §14 is satisfied by a faithfully recorded miss against a wrong deadline.
Chapter 28.4 takes the next family, and it shares this chapter's most uncomfortable feature. Refresh telemetry can report a clean obligation while data is corrupt; calibration can report success while producing a result that fails at runtime — and 28.1 already depended on exactly that, because its hypothesis C took a prerequisite step's committed flag on trust. The question splits five ways: whether calibration failed to start, failed to converge, failed to commit, became stale, or committed a result that was never robust.
Continue learning
Related tutorials
- Related topic
The Refresh Requirement
Leakage produces a rule about the passage of time rather than about any operation. What the maintenance operation actually does, why it costs device availability, and how a digital design tracks a deadline, arbitrates it against traffic, and proves it never silently drops the obligation.
- Related topic
The Memory-Subsystem View
Six levels, six shared resources, six conflicts. Assembling them into one picture and tracing a single request through it produces the question that organises all memory debugging: which level is blocking this, and how often?
- Related topic
Refresh (REF)
Refresh is the first command whose legality depends on more than one bank, and the first that occupies the device rather than requesting a transfer. DDR5's same-bank variant exists precisely to weaken that precondition.
- Related topic
Performance Impact
Two request streams with identical addresses and counts can demand more than twice the row-state work, decided only by their order — and the instrumentation that measures it lies in specific, recognisable ways.
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.
