DDR · Module 28
Timing Violations
A violation names its own evidence: two commands, one shared resource, a required separation and an actual one. Reconstructing those four facts is the whole investigation.
Chapter 28.1 debugged a search that did not succeed, and its difficulty was that a pass bitmap is a projection: one signature, several causes, no way to tell them apart without an experiment.
A timing violation is a different kind of object, and the difference is good news. A timing rule is a statement about two commands and one resource, so a violation names its own evidence: which command, which earlier command, which resource they share, and what separation the device required.
The investigation is therefore a reconstruction, not a hunt. Four facts, all of them recoverable, and the failure is localised the moment you have them.
What makes it hard is that three of the four are easy to get wrong. The earlier command must be the earlier command on the same resource, which requires resource identity the controller may have classified incorrectly. The required separation comes through a chain of unit conversions any link of which can be off. And the actual separation is counted between two events that must both be the commit — not a request, not a candidate, not a grant.
1. The Four Events, and Which One Legality Uses
Chapter 17.1 owns this distinction and earns its place on it: the commit point is the single instant at which architectural state is permitted to change. Timing debug depends on it completely.
| Event | What it is | Does timing legality use it? |
|---|---|---|
| Request | an upstream transaction arrives — 17.5 | no |
| Candidate | the scheduler considers an entry this cycle — 17.4 | no |
| Grant | arbitration selects it | no |
| Commit / issue | the command is driven on the bus | yes — only this |
A timing contract is between two committed commands. Nothing else. A request that waited ten cycles in a queue contributes nothing to spacing; a candidate evaluated and rejected five times contributes nothing.
And this is the single most productive place to look when a violation makes no sense. CURRICULUM-DERIVED from 17.4's mask structure: a scheduler evaluates many candidates per cycle and commits at most one. A history recorder wired to the candidate signal instead of the commit signal records commands that never happened — and then every reconstruction built on it is wrong in a way that looks like a device problem.
ONE CYCLE INSIDE A SCHEDULER, and what each event would record
cycle 41 queue entries considered : 6 (candidates)
legality mask passes : 3
policy mask selects : 1 (grant)
driven on the bus : 1 (commit)
a recorder on `candidate` logs 6 commands for cycle 41.
a recorder on `grant` logs 1 -- and logs it even if a later
stage squashed it.
a recorder on `commit` logs 1, and only if it actually issued.
only the third is the event a timing rule is about.So the first question in any timing investigation is which signal the evidence came from, and §17's recorder exists to make that answerable rather than assumed.
2. What a Contract Is Between — Resource Identity
Chapter 13.3 owns the scope taxonomy and, in its own words, the two opposite bugs that follow from getting it wrong. This chapter debugs those two bugs.
Every timing rule has a scope: the set of command pairs it governs. CURRICULUM-DERIVED from 13.3 and 14.6:
| Scope | Example rule | The pair it governs |
|---|---|---|
| Same bank | tRCD, tRP, tRAS — 14.1, 14.2 | two commands to one bank |
| Same bank group | tCCD_L — 14.6 | two column commands in one group |
| Different bank group | tCCD_S — 14.6 | two column commands across groups |
| Cross-bank | tRRD_S / tRRD_L — 14.7 | two activates, group-dependent |
| Device-wide rolling | tFAW — 14.8 | activates in a window, any bank |
So applying the right rule requires classifying the resource relationship correctly, and a misclassification produces one of two opposite failures:
Too narrow a scope — the controller thinks two commands are unrelated when they share a resource. It applies the shorter rule or none, commits early, and violates a contract it never evaluated. This is the dangerous direction: the violation is real and the controller believes it is legal.
Too wide a scope — the controller thinks two commands share a resource when they do not. It applies the longer rule, waits unnecessarily, and loses bandwidth while remaining legal. Chapter 14.6 owns the performance argument; for debugging, the signature is a performance complaint with no violation.
The asymmetry matters for how you read evidence. A reported violation is consistent with a genuine spacing bug and with a scope that was too narrow. A performance shortfall with zero violations is consistent with a scope that was too wide — and nobody investigates it, because nothing failed.
3. The Reconstruction — Six Fields
Here is the whole investigation, as data. A violation is localised when these six fields are known, and not before.
| Field | Where it comes from | Failure mode if wrong |
|---|---|---|
| 1. Violating command | the commit that triggered the check | a candidate logged as a commit — §1 |
| 2. Previous relevant command | the last commit on the same resource | the last commit anywhere — §5 |
| 3. Resource identity | bank, bank group, rank of both | the wrong rule applied — §2 |
| 4. Required separation | the parameter, converted — §11 | off-by-one, wrong unit, wrong generation |
| 5. Actual separation | commit cycle minus commit cycle | boundary convention — §10 |
| 6. Violated rule | the named parameter | a generic "timing error" that localises nothing |
Field 2 is the one that is usually wrong, and §5 is about why. A history recorder that returns the previous command is answering a different question from the previous command on the same resource, and for every scope in §2's table except device-wide, the second is the only one that matters.
Field 6 deserves a sentence of its own. A report saying timing violation at cycle 4181 is not a localization. A report saying tCCD_L required 8 and observed 4, between the read committed at 4177 to bank 2 group 0 and the read committed at 4181 to bank 3 group 0 is one — and the difference is what the rest of this chapter is about producing.
4. Earliest Legal Time Is a Maximum
Chapter 13.3 owns this derivation and it is the reason a single violated rule is not the end of the investigation.
A command is legal when it satisfies every applicable constraint. So the earliest legal issue time is the maximum over all of them, and a command that violates one rule may be violating several.
CURRICULUM-DERIVED consequence for debugging: the rule your checker reported is the rule your checker evaluated first, not necessarily the binding one.
a read is committed at cycle 4181. three rules apply:
tRCD from ACT at 4174 to this bank : earliest legal 4180 ok
tCCD_L from RD at 4177, same group : earliest legal 4185 VIOLATED by 4
tCCD_S from RD at 4179, other group : earliest legal 4183 VIOLATED by 2
earliest legal = max(4180, 4185, 4183) = 4185.
a checker that reports only tCCD_S understates the violation by 2
cycles AND names the wrong rule. A checker that reports only the
FIRST failing rule it evaluates depends on evaluation order.
the honest report is the binding constraint -- the maximum -- plus
every rule that failed. §17's snapshot records both.So a violation report that names one rule may be hiding a worse one, and the discriminating question is always what was the maximum. §17's snapshot therefore captures a rule mask rather than a single rule identifier.
5. The Previous Command Is Not the Previous Command
Field 2, and the error class that makes reconstruction hard.
Consider a controller interleaving across four banks. The command stream is dense and consecutive commands rarely touch the same bank. CURRICULUM-DERIVED from 16.1, that interleaving is the point — it is where bank-level parallelism comes from.
commits, most recent last:
4170 ACT bank 0 grp 0
4173 RD bank 0 grp 0
4175 ACT bank 5 grp 1
4177 RD bank 2 grp 0
4179 RD bank 5 grp 1
4181 RD bank 3 grp 0 <-- the violating command
"the previous command" is the RD at 4179, bank 5, group 1.
DIFFERENT group -> tCCD_S applies -> separation 2, and if
tCCD_S is 2 (ILLUSTRATIVE) this looks LEGAL.
"the previous column command in the SAME bank group" is the RD at
4177, bank 2, group 0 -> tCCD_L applies -> separation 4, and if
tCCD_L is 8 (ILLUSTRATIVE) this is a violation by 4 cycles.
the same command stream is legal or illegal depending on which
question the recorder answers.So a history recorder must be queryable by resource, not merely ordered by time. §18's recorder keeps per-resource last-commit timestamps alongside a bounded chronological trace, because the two answer different questions and both are needed: the per-resource table localises the rule, and the chronological trace shows the context a human needs to believe it.
6. A Misclassified Bank Group, Cycle by Cycle
The §5 case as the checker sees it. ILLUSTRATIVE values: tCCD_S = 2, tCCD_L = 8, both in command clocks; 14.6 owns the real figures and their generation labelling.
A same-group column pair classified as cross-group
10 cyclesRead clsf_same as the defect, not the symptom. Both commands carry commit_grp = 0, so they are in the same bank group — and the classifier output says otherwise. The controller then applied tCCD_S = 2, found 4 ≥ 2, and committed believing it was legal.
The required lane shows 2 while the truth is 8, and that single field is the localization. DERIVED: observed separation 5 − 1 = 4; required under the correct rule 8; shortfall 4 cycles.
And note what a naive report would have said. Without clsf_same and commit_grp side by side, the evidence is a read at cycle 5 violated column spacing — which is consistent with a wrong parameter value, a wrong conversion, an off-by-one, and this misclassification. Publishing the classification alongside the requirement is what discriminates them, and it is the reason §17's snapshot stores the resource fields of both commands rather than only the violating one.
7. The Reconstruction as a Flow
Three things the flow makes visible.
Only the commit reaches the recorder. §1's argument drawn: there is no arrow from a request, a candidate or a grant, and adding one would make every downstream reconstruction describe commands that never issued.
The rule evaluator queries by resource, not by recency. §5's argument drawn: the arrow asks previous on this resource, which is a different query from previous, and the per-resource table exists because the chronological trace cannot answer it in bounded time.
And the maximum is taken before anything is reported. §4's argument drawn: a report naming the first failing rule depends on evaluation order, so the evaluator resolves the maximum and forwards every failing rule.
8. Nine Causes, and What Distinguishes Them
The reconstruction localises the rule. This table localises the defect, and the right-hand column is what makes each hypothesis testable rather than plausible.
| # | Cause | Signature | Discriminating observation |
|---|---|---|---|
| 1 | Off-by-one — boundary convention | violation is exactly 1 cycle | shortfall is always 1, across every instance and every rule |
| 2 | Wrong parameter value | one rule violated, others clean | the applied count differs from the converted device figure — §11 |
| 3 | Wrong unit conversion | violations scale with frequency | recompute the chain; the error is proportional, not constant — §11 |
| 4 | Bank misclassification | same-bank rule never applied | the two commands' bank fields are equal and the classifier says otherwise |
| 5 | Bank-group misclassification | tCCD_L/tRRD_L never applied | as §6 — group fields equal, classifier disagrees |
| 6 | Stale timing history | violation after an idle gap or a reset | the previous-command timestamp predates the last reset — §13 |
| 7 | Simultaneous events | violation only at back-to-back commits | two commits in one cycle, or a commit in the cycle a counter reloads — §14 |
| 8 | Candidate counted as commit | violations that reference commands never on the bus | the referenced command is absent from a bus-level trace — §1 |
| 9 | Generation mismatch | a group-dependent rule applied to a device without groups | the configured device organisation versus the rule set in use — §16 |
Row one is worth checking first because it is free. If every violation across every rule is short by exactly one cycle, the defect is a shared boundary convention, not nine separate parameter errors. §10 is about that convention.
Row three has a signature people miss. A unit-conversion error is proportional: it gets worse at higher frequency because the same time converts to more clocks. So a violation that appears only at the top frequency bin and scales with it is a conversion hypothesis, while one that is a constant number of cycles at every frequency is a parameter or boundary hypothesis. That single observation separates rows 1–2 from row 3, and it costs one extra run at a lower frequency.
9. The Boundary Convention
Cause 1, and it deserves its own section because it is the cheapest bug to have and the easiest to introduce.
A spacing rule says two commands must be separated by at least N. The question the convention answers is N what, measured how — and there are three defensible readings of the same sentence.
| Reading | Commands at cycles a and b are legal when | N = 4, a = 10: earliest legal b |
|---|---|---|
| Cycles between the commands | b − a − 1 ≥ N | 15 |
| Cycles from the first to the second | b − a ≥ N | 14 |
The second may issue on cycle a + N | b ≥ a + N | 14 |
Readings two and three agree; reading one differs by one. So a controller that counts cycles between against a parameter specified as cycles from issue to issue is legal by exactly one cycle less than it should be, on every rule, forever.
CURRICULUM-DERIVED: Chapter 13.3 owns the derivation of earliest legal time and the countdown-versus-timestamp representation choice, and the two representations fail this differently:
A countdown loaded with N and decremented each cycle permits issue when it reaches zero. Whether that is a + N or a + N + 1 depends on whether it is loaded on the issue cycle or the cycle after — a one-line difference with a one-cycle consequence.
A timestamp comparison b ≥ last + N has the convention written where a reader can see it. That is the debugging argument for timestamps over countdowns, independent of the performance argument 13.3 makes.
The discriminating observation is the shortfall distribution. Collect every violation's shortfall. All ones means a convention; a spread means a parameter or classification problem — and §17's snapshot records the shortfall precisely so this is a query rather than a re-run.
10. Provenance — From a Device Figure to an Applied Count
Cause 2 and cause 3 both live in a chain, and debugging them means walking it.
The chain, in the direction the value travels:
device specification figure
| a time, or a count, or the MAXIMUM of both -- §12
v
firmware / software value
| chosen per frequency bin
v
unit conversion time -> command clocks
| DIVIDE by the clock period, then ROUND
v
rounding direction
| a MINIMUM time must round UP
v
register encoding
| encoded field, often offset from the true count
v
controller interpretation
| counted in command clocks, or controller clocks?
v
applied separationSix links, and each has a characteristic failure.
| Link | Failure | Signature |
|---|---|---|
| Specification | the figure is a max(nCK, ns) and only one term was taken | frequency-dependent — §12 |
| Software value | the wrong frequency bin's table | violation only in one bin |
| Conversion | ps treated as ns, or the reciprocal used | wrong by a factor, not by one |
| Rounding | a minimum rounded down | short by exactly one, intermittently |
| Encoding | the field is offset and the offset was applied twice, or not at all | constant offset across all rules sharing the encoding |
| Interpretation | command clocks versus controller clocks | wrong by the clock ratio |
The rounding row is the subtle one and the rule is not symmetric. A minimum time requirement must round up to the next whole clock: rounding down produces a separation shorter than the device requires. So ceil is correct for minimums and floor is a bug — and the signature is a one-cycle shortfall that appears only at frequencies where the division is not exact.
That last clause is the discriminating detail. A floor bug is invisible whenever the parameter divides evenly into the clock period and appears whenever it does not. So a violation that appears at some frequencies and not others, short by exactly one, with a clean conversion at the passing frequencies, is a rounding-direction hypothesis — and re-deriving the number by hand at a failing frequency confirms or kills it in minutes.
No controller register encoding is asserted here. The encoding row is real and its offset conventions are vendor-specific; the debugging instruction is to read the value back from the register and compare it against your own conversion, not to assume any particular encoding.
11. The Dual-Term Trap
Chapter 14.7 owns the max(nCK, ns) dual-term specification form on a real verified parameter, and the crossover between the two terms — which is exactly what makes it a debugging hazard.
A parameter specified as the maximum of a clock count and a time has two regimes. At low frequency the time term dominates and converts to more clocks than the count; at high frequency the count term dominates.
DERIVED under a stated ILLUSTRATIVE model — a parameter specified as max(4 nCK, 7.5 ns):
clock period time term in clocks count term binding term
----------- ------------------- ---------- ------------
2.50 ns ceil(7.5 / 2.50) = 3 4 count (4)
1.875 ns ceil(7.5 / 1.875) = 4 4 equal (4)
1.25 ns ceil(7.5 / 1.25) = 6 4 time (6)
0.9375 ns ceil(7.5/0.9375) = 8 4 time (8)
a controller that implements ONLY the count term applies 4 at
every frequency. It is correct at the first two rows and
violates by 2 and by 4 at the last two.
a controller that implements ONLY the time term applies 3 at the
first row, where the count term requires 4 -- so it violates by 1
at LOW frequency and is correct at high frequency.Both single-term implementations produce frequency-dependent violations, and they fail at opposite ends. That is the discriminating observation: count-only fails at high frequency and scales with it; time-only fails at low frequency by a small constant.
And this is why §8's callout ranks physical margin last for a spacing violation. A frequency-dependent spacing violation has three digital explanations before any physical one — conversion, bin selection, and dual-term crossover — and all three are cheaper to exclude than a channel measurement.
12. Stale History and the Reset Boundary
Cause 6. A timing rule is a statement about the past, so the rule is only as good as the history it consults.
Three ways the history becomes a liar.
A reset that clears commands but not timestamps. The per-resource table still holds a last-commit cycle from before the reset, and the first command after reset is compared against it. The signature is a violation on the first command to a resource after a reset, and it is usually reported as a spurious failure and cleared — which is the module's third law being broken.
A counter that wrapped. If the cycle counter is narrow and a resource is idle for longer than its range, now − last computes a small number from a large gap. The signature is a violation after a long idle period, and it is the opposite of a real timing problem: the actual separation was enormous.
A frequency change without invalidation. The timestamps are in clocks, and the clock changed. Every stored timestamp is now in the wrong unit — which is 28.4's staleness problem arriving in the timing domain.
| Observation | Supports | Discriminating experiment |
|---|---|---|
| Violation on the first command after reset | timestamps not cleared | reset with a long idle before the first command; the violation should not move |
| Violation after a long idle | counter wrap | widen the counter, or saturate it; a saturating counter cannot produce a small gap |
| Violation only after a frequency change | timestamps in stale units | change frequency, then idle past every window before resuming |
Row two's fix is worth stating because it is structural, not a tuning change. A saturating elapsed counter cannot wrap, so the comparison elapsed ≥ required becomes monotone-safe: once a resource has been idle long enough, it stays legal. §17's recorder saturates for this reason.
13. Simultaneous Events
Cause 7, and the class of bug that only appears at full throughput.
Two things can coincide in one cycle, and each breaks a naive checker.
A commit in the same cycle a counter reloads. If the elapsed counter for a resource is reset on commit and compared in the same cycle, the order of the two operations decides the answer. Reading the post-reset value makes the separation zero and every back-to-back pair a violation; reading the pre-reset value is correct. This is the stale-register hazard, and §17 handles it by comparing against the registered previous timestamp rather than a value being written this cycle.
Two commits in one cycle. A controller with multiple sub-channels or independent ranks may commit more than one command per cycle. CURRICULUM-DERIVED from 25.2: sub-channels are independent, so two commits in one cycle are legal — and a recorder with one write port per cycle silently drops one of them.
The dropped command then becomes invisible to every later reconstruction, which produces violations that reference the wrong previous command. §17's recorder therefore publishes a dropped count rather than overwriting silently, on the principle that a recorder which loses evidence must say so.
14. Generation Mismatch
Cause 9, and the one that requires explicit scoping.
Bank groups are not universal. CURRICULUM-DERIVED from 5.3 and 16.4: the bank-group organisation and the _L/_S rule split that depends on it are generation-specific, and 14.6 labels its same-group/different-group split with the generation explicitly for that reason.
So a rule set is only correct for the device organisation it was written for.
| Mismatch | Consequence | Signature |
|---|---|---|
| Group-dependent rules applied to a device without groups | every pair classified as same-group; the long rule always applies | no violations, bandwidth well below expectation |
| Group-independent rules applied to a device with groups | tCCD_L never applied | real violations the controller believes are legal — §6 |
| The wrong number of groups configured | banks assigned to wrong groups | violations correlated with specific bank pairs |
Row two is §6's bug with a different root cause, and the distinction matters for the fix. §6's defect was a classifier computing the relationship incorrectly for a device that has groups. This row is a rule set that has no concept of groups at all — and the discriminating observation is whether tCCD_L is ever applied to any pair. If the long rule has zero applications across a long run, the controller does not implement it, which §17's rule mask makes a one-query check rather than a code review.
Row three's signature is the most localised of the three. Violations that correlate with specific bank pairs rather than with frequency, idle time, or reset point point at a group-assignment table — and the pairs that fail tell you which entries are wrong.
15. The Violation Snapshot
// ---------------------------------------------------------------------
// timing_violation_snapshot -- CLASSIFICATION: synthesisable, BINDABLE.
//
// WHAT IT DOES: latches the FIRST timing violation with §3's six
// fields, plus the resource identity of BOTH commands, and counts
// subsequent violations without overwriting the first.
//
// WHY FIRST-WINS: Module 28's laws. A timing violation cascades -- one
// early commit puts every later command in a wrong relationship -- so
// the hundredth violation is a consequence and the first is evidence.
//
// WHY BOTH COMMANDS' RESOURCE FIELDS (§2, §6): a misclassification
// produces a correctly recorded violation of the WRONG rule. Storing
// prev_grp alongside cur_grp is what makes "these were the same group
// and the classifier said otherwise" a readable fact rather than an
// inference.
//
// WHY A RULE MASK AND NOT A RULE ID (§4): earliest legal time is a
// maximum, so several rules can fail at once. Recording one id makes
// the report depend on the evaluator's evaluation order.
//
// WHAT IT CANNOT TELL YOU: whether the rule was the right rule.
//
// SYNTHESIS: one set of capture registers, two counters, no arrays.
// ---------------------------------------------------------------------
module timing_violation_snapshot #(
parameter int BANKS = 16,
parameter int GROUPS = 4,
parameter int RULES = 8, // one bit per named rule
parameter int CYC_W = 32
)(
input logic clk,
input logic rst_n,
// ---- commit interface (observed only; §1 -- never candidate/grant)
input logic commit,
input logic [3:0] commit_cmd,
input logic [$clog2(BANKS)-1:0] commit_bank,
input logic [$clog2(GROUPS)-1:0] commit_grp,
input logic [CYC_W-1:0] cycle_now,
// ---- the violation report from the controller's rule evaluator
input logic viol,
input logic [RULES-1:0] viol_rule_mask,
input logic [15:0] viol_required,
input logic [15:0] viol_observed,
input logic [3:0] prev_cmd,
input logic [$clog2(BANKS)-1:0] prev_bank,
input logic [$clog2(GROUPS)-1:0] prev_grp,
input logic [CYC_W-1:0] prev_cycle,
input logic clsf_same_bank,
input logic clsf_same_group,
input logic clear,
// ---- published evidence
output logic valid,
output logic [RULES-1:0] snap_rule_mask,
output logic [15:0] snap_required,
output logic [15:0] snap_observed,
output logic [15:0] snap_shortfall,
output logic [3:0] snap_cur_cmd,
output logic [$clog2(BANKS)-1:0] snap_cur_bank,
output logic [$clog2(GROUPS)-1:0] snap_cur_grp,
output logic [3:0] snap_prev_cmd,
output logic [$clog2(BANKS)-1:0] snap_prev_bank,
output logic [$clog2(GROUPS)-1:0] snap_prev_grp,
output logic [CYC_W-1:0] snap_cur_cycle,
output logic [CYC_W-1:0] snap_prev_cycle,
output logic snap_clsf_same_bank,
output logic snap_clsf_same_group,
output logic snap_misclassified,
output logic [15:0] viol_count,
output logic [RULES-1:0] rules_ever_violated,
output logic [RULES-1:0] rules_ever_applied_in
);
// $clog2(1) is 0, which makes every index vector illegal as [-1:0].
// A power-of-two test alone accepts 1, so the bound is explicit.
initial begin
if (BANKS < 2) $fatal(1, "timing_violation_snapshot: BANKS must be >= 2 (got %0d)", BANKS);
if (GROUPS < 2) $fatal(1, "timing_violation_snapshot: GROUPS must be >= 2 (got %0d)", GROUPS);
if (RULES < 1) $fatal(1, "timing_violation_snapshot: RULES must be >= 1");
if (CYC_W < 8) $fatal(1, "timing_violation_snapshot: CYC_W must be >= 8");
if ((BANKS % GROUPS) != 0)
$fatal(1, "timing_violation_snapshot: %0d banks do not divide into %0d groups", BANKS, GROUPS);
end
always_ff @(posedge clk) begin
if (!rst_n || clear) begin
valid <= 1'b0;
snap_rule_mask <= '0;
snap_required <= '0;
snap_observed <= '0;
snap_shortfall <= '0;
snap_cur_cmd <= '0;
snap_cur_bank <= '0;
snap_cur_grp <= '0;
snap_prev_cmd <= '0;
snap_prev_bank <= '0;
snap_prev_grp <= '0;
snap_cur_cycle <= '0;
snap_prev_cycle <= '0;
snap_clsf_same_bank <= 1'b0;
snap_clsf_same_group <= 1'b0;
snap_misclassified <= 1'b0;
viol_count <= '0;
rules_ever_violated <= '0;
rules_ever_applied_in<= '0;
end else begin
// §14: count every violation, saturating rather than wrapping. A
// wrapped count that reads 3 after 65,539 violations would
// understate a cascade by four orders of magnitude.
if (viol && viol_count != 16'hFFFF) viol_count <= viol_count + 1'b1;
if (viol) rules_ever_violated <= rules_ever_violated | viol_rule_mask;
// §16 row two: a rule the controller never applies to ANY pair is
// a rule it does not implement. Accumulating the applied mask
// makes that a one-query check instead of a code review.
if (commit) rules_ever_applied_in <= rules_ever_applied_in | viol_rule_mask;
// ---- FIRST violation wins. Nothing but `clear` releases it.
if (viol && !valid) begin
valid <= 1'b1;
snap_rule_mask <= viol_rule_mask;
snap_required <= viol_required;
snap_observed <= viol_observed;
// §10: the shortfall distribution separates a boundary
// convention (always 1) from a parameter or classification
// problem (a spread). Computed here so it is queryable.
snap_shortfall <= (viol_required > viol_observed)
? (viol_required - viol_observed) : 16'd0;
snap_cur_cmd <= commit_cmd;
snap_cur_bank <= commit_bank;
snap_cur_grp <= commit_grp;
snap_prev_cmd <= prev_cmd;
snap_prev_bank <= prev_bank;
snap_prev_grp <= prev_grp;
snap_cur_cycle <= cycle_now;
snap_prev_cycle <= prev_cycle;
snap_clsf_same_bank <= clsf_same_bank;
snap_clsf_same_group <= clsf_same_group;
// §6's localization, computed rather than left to a reader:
// the resource fields say same, the classifier says different.
// This is the ONE derived bit in the block, and it exists
// because the comparison is what turns six recorded fields
// into a named defect.
snap_misclassified <= ((commit_bank == prev_bank) && !clsf_same_bank)
|| ((commit_grp == prev_grp) && !clsf_same_group);
end
end
end
endmodulesnap_misclassified is the only derived bit in the block, and it earns its place: it compares the recorded resource fields of the two commands against what the classifier claimed. §6's defect is invisible in a report that records only the violated rule and the two counts — and one bit turns it into a named finding.
rules_ever_applied_in answers §14 row two with a query. A rule set that never applies tCCD_L to any pair does not implement it, and the accumulated mask says so after a long run. That is a different defect from a rule applied incorrectly, and no per-violation record distinguishes them.
And viol_count saturates rather than wrapping. A cascade produces tens of thousands of violations; a 16-bit wrapping counter reading 3 would understate it catastrophically. Saturation is honest at the cost of precision, which is the right trade for a number whose only use is how big was the cascade.
16. The Bounded Command History
// ---------------------------------------------------------------------
// commit_history -- CLASSIFICATION: synthesisable, BINDABLE.
//
// A BOUNDED recorder. Not a logic analyser: DEPTH records, oldest
// overwritten, and the loss is PUBLISHED rather than silent.
//
// WHY TWO STRUCTURES (§5): "the previous command" and "the previous
// command on the same resource" are different queries. The
// chronological ring answers the first and gives a human context; the
// per-resource table answers the second, which is the one a timing
// rule needs. Neither substitutes for the other.
//
// WHY SATURATING ELAPSED (§13): a narrow cycle counter that wraps
// computes a SMALL gap from a LONG idle, producing a violation whose
// actual separation was enormous. A saturating elapsed value cannot:
// once a resource has been idle past the widest window, it stays
// legal.
//
// WHY `dropped` IS PUBLISHED (§14): 25.2 establishes that sub-channels
// are independent, so two commits in one cycle are legal. A recorder
// with one write port drops one -- and a recorder that loses evidence
// must say so.
//
// SYNTHESIS: one ring buffer, one per-resource register file, two
// counters. No hierarchical references, no DUT handle.
// ---------------------------------------------------------------------
module commit_history #(
parameter int DEPTH = 32,
parameter int BANKS = 16,
parameter int GROUPS = 4,
parameter int ELAPS_W = 16 // saturating elapsed width
)(
input logic clk,
input logic rst_n,
input logic commit, // §1: commit ONLY
input logic commit_2nd, // a second commit this cycle
input logic [3:0] commit_cmd,
input logic [$clog2(BANKS)-1:0] commit_bank,
input logic [$clog2(GROUPS)-1:0] commit_grp,
input logic commit_is_column, // RD or WR
input logic clear,
// ---- per-resource query (combinational read)
input logic [$clog2(BANKS)-1:0] q_bank,
input logic [$clog2(GROUPS)-1:0] q_grp,
output logic [ELAPS_W-1:0] q_elapsed_bank,
output logic [ELAPS_W-1:0] q_elapsed_grp_col,
output logic q_bank_seen,
output logic q_grp_col_seen,
// ---- published evidence
output logic [$clog2(DEPTH)-1:0] wr_ptr,
output logic [15:0] wrapped,
output logic [15:0] dropped,
output logic [15:0] total_commits
);
initial begin
if (DEPTH < 2) $fatal(1, "commit_history: DEPTH must be >= 2 (got %0d)", DEPTH);
if (BANKS < 2) $fatal(1, "commit_history: BANKS must be >= 2");
if (GROUPS < 2) $fatal(1, "commit_history: GROUPS must be >= 2");
if (ELAPS_W < 4) $fatal(1, "commit_history: ELAPS_W must be >= 4");
end
localparam logic [ELAPS_W-1:0] ELAPS_MAX = {ELAPS_W{1'b1}};
// ---- chronological ring: context for a human reading the report
logic [3:0] h_cmd [DEPTH];
logic [$clog2(BANKS)-1:0] h_bank [DEPTH];
logic [$clog2(GROUPS)-1:0] h_grp [DEPTH];
logic h_col [DEPTH];
// ---- per-resource elapsed: the query a timing rule actually needs
logic [ELAPS_W-1:0] e_bank [BANKS];
logic [ELAPS_W-1:0] e_grp_col [GROUPS];
logic s_bank [BANKS];
logic s_grp_col [GROUPS];
always_ff @(posedge clk) begin
if (!rst_n || clear) begin
wr_ptr <= '0;
wrapped <= '0;
dropped <= '0;
total_commits <= '0;
// §13: clearing the SEEN flags is what prevents a stale
// timestamp from being compared against the first command after
// a reset. Clearing the elapsed values alone would leave
// "elapsed = 0, seen = 1", which reads as a commit last cycle.
for (int b = 0; b < BANKS; b++) begin e_bank[b] <= '0; s_bank[b] <= 1'b0; end
for (int g = 0; g < GROUPS; g++) begin e_grp_col[g] <= '0; s_grp_col[g] <= 1'b0; end
end else begin
// ---- age every resource by one cycle, saturating. Done FIRST so
// a commit this cycle overwrites the aged value below rather
// than being aged itself.
for (int b = 0; b < BANKS; b++)
if (s_bank[b] && e_bank[b] != ELAPS_MAX) e_bank[b] <= e_bank[b] + 1'b1;
for (int g = 0; g < GROUPS; g++)
if (s_grp_col[g] && e_grp_col[g] != ELAPS_MAX) e_grp_col[g] <= e_grp_col[g] + 1'b1;
if (commit) begin
h_cmd [wr_ptr] <= commit_cmd;
h_bank[wr_ptr] <= commit_bank;
h_grp [wr_ptr] <= commit_grp;
h_col [wr_ptr] <= commit_is_column;
// Oldest-overwritten ring. `wrapped` counts how many whole
// laps of context have been lost.
if (wr_ptr == DEPTH - 1) begin
wr_ptr <= '0;
if (wrapped != 16'hFFFF) wrapped <= wrapped + 1'b1;
end else wr_ptr <= wr_ptr + 1'b1;
// Elapsed resets to ZERO on the commit cycle, and because this
// assignment comes after the ageing loop above, it wins. A
// rule comparing `q_elapsed` next cycle therefore sees 1, not
// 0 -- which is §9's convention, made explicit in one place.
e_bank[commit_bank] <= '0;
s_bank[commit_bank] <= 1'b1;
if (commit_is_column) begin
e_grp_col[commit_grp] <= '0;
s_grp_col[commit_grp] <= 1'b1;
end
if (total_commits != 16'hFFFF) total_commits <= total_commits + 1'b1;
end
// §14: a second commit in the same cycle is legal and this
// recorder cannot store it. Silence would corrupt every later
// reconstruction; the count makes the gap visible.
if (commit && commit_2nd && dropped != 16'hFFFF)
dropped <= dropped + 1'b1;
end
end
// Combinational query. `seen` is separate from `elapsed` because
// "never committed" and "committed a long time ago" are different
// answers, and zero cannot represent both -- the same three-valued
// discipline 27.3 §6 and 28.1 §17 arrived at.
always_comb begin
q_elapsed_bank = e_bank[q_bank];
q_elapsed_grp_col = e_grp_col[q_grp];
q_bank_seen = s_bank[q_bank];
q_grp_col_seen = s_grp_col[q_grp];
end
endmoduleseen is separate from elapsed, and that is the same three-valued discipline this curriculum keeps arriving at. A resource never committed to and a resource committed to long ago are different answers, and zero cannot represent both — 27.3 §6 established it for bank state, 27.4 §2 for expected data, 28.1 §17 for a pass index. Here it is what stops the first command after a reset from being compared against a phantom predecessor.
Ageing happens before the commit reset, deliberately. Both are non-blocking assignments to the same registers in one block, so the later assignment wins — and the comment states the resulting convention explicitly, because §9 shows that a one-line ordering choice is a one-cycle timing convention.
And dropped is the recorder admitting a limit. Chapter 25.2 establishes that sub-channels are independent, so two commits in one cycle are legal DDR5 behaviour. A single-port recorder cannot hold both, and a recorder that loses evidence silently makes every later reconstruction untrustworthy without saying so.
17. What the Assertions Prove
// Bound to §15's snapshot and §16's recorder. Every property carries
// `disable iff (!rst_n)`, and every antecedent is published as a cover
// below -- 27.2 measured this curriculum at 78.53% implications, so a
// silent pass is otherwise indistinguishable from an unbound checker.
module timing_debug_sva #(
parameter int RULES = 8,
parameter int DEPTH = 32
)(
input logic clk, rst_n, clear,
input logic commit, commit_2nd, viol,
input logic [RULES-1:0] viol_rule_mask, snap_rule_mask, rules_ever_violated,
input logic [15:0] viol_required, viol_observed,
input logic [15:0] snap_required, snap_observed, snap_shortfall,
input logic [15:0] viol_count, wrapped, dropped, total_commits,
input logic valid, snap_misclassified,
input logic [$clog2(DEPTH)-1:0] wr_ptr,
input logic q_bank_seen, q_grp_col_seen,
input logic [15:0] q_elapsed_bank
);
// ---- P1. FIRST VIOLATION WINS. Nothing but `clear` changes the
// snapshot. A timing violation cascades, so the hundredth record is
// a consequence and the first is the evidence.
property p_first_violation_sticky;
@(posedge clk) disable iff (!rst_n)
(valid && !clear) |=> (snap_rule_mask == $past(snap_rule_mask)
&& snap_required == $past(snap_required)
&& snap_observed == $past(snap_observed));
endproperty
assert property (p_first_violation_sticky)
else $error("snapshot: a later violation overwrote the first");
// ---- P2. FORBIDDEN. A later violation must not clear `valid`.
property p_valid_sticky;
@(posedge clk) disable iff (!rst_n)
(valid && !clear) |=> valid;
endproperty
assert property (p_valid_sticky)
else $error("snapshot: valid dropped without a clear");
// ---- P3. Clear releases. A latch with no release silently stops
// recording after the first failure of the first boot.
property p_clear_releases;
@(posedge clk) disable iff (!rst_n)
clear |=> (!valid && snap_rule_mask == '0);
endproperty
assert property (p_clear_releases)
else $error("snapshot: clear did not release");
// ---- P4. The snapshot captures on the FIRST violation, and the
// captured mask is that violation's mask -- not a later one's.
property p_capture_is_first;
@(posedge clk) disable iff (!rst_n)
(viol && !valid) |=> (snap_rule_mask == $past(viol_rule_mask));
endproperty
assert property (p_capture_is_first)
else $error("snapshot: captured mask is not the first violation's");
// ---- P5. §10's shortfall is required minus observed, and never
// negative. A negative shortfall would mean the evaluator reported a
// violation on a command that met its requirement.
property p_shortfall_arithmetic;
@(posedge clk) disable iff (!rst_n)
valid |-> (snap_shortfall == ((snap_required > snap_observed)
? (snap_required - snap_observed) : 16'd0));
endproperty
assert property (p_shortfall_arithmetic)
else $error("snapshot: shortfall %0d inconsistent with %0d/%0d",
snap_shortfall, snap_required, snap_observed);
// ---- P6. FORBIDDEN. A violation is never reported with observed
// greater than or equal to required. That combination means the rule
// evaluator and the snapshot disagree about what failed.
property p_violation_is_a_shortfall;
@(posedge clk) disable iff (!rst_n)
viol |-> (viol_observed < viol_required);
endproperty
assert property (p_violation_is_a_shortfall)
else $error("violation reported with observed %0d >= required %0d",
viol_observed, viol_required);
// ---- P7. FORBIDDEN. A violation always names at least one rule. A
// zero mask is a violation that localises nothing -- §3 field 6.
property p_violation_names_a_rule;
@(posedge clk) disable iff (!rst_n)
viol |-> (viol_rule_mask != '0);
endproperty
assert property (p_violation_names_a_rule)
else $error("violation reported with an empty rule mask");
// ---- P8. The accumulated mask is monotone: a rule once violated
// stays recorded. §14 row two depends on the accumulation.
property p_ever_violated_monotone;
@(posedge clk) disable iff (!rst_n)
(!clear) |=> ((rules_ever_violated & $past(rules_ever_violated))
== $past(rules_ever_violated));
endproperty
assert property (p_ever_violated_monotone)
else $error("rules_ever_violated lost a bit");
// ---- P9. INVARIANT. viol_count saturates and never wraps. A
// wrapped count reading 3 after 65,539 violations would understate a
// cascade by four orders of magnitude.
property p_count_saturates;
@(posedge clk) disable iff (!rst_n)
(viol_count == 16'hFFFF) |=> (viol_count == 16'hFFFF);
endproperty
assert property (p_count_saturates)
else $error("viol_count wrapped past saturation");
// ---- P10. FORBIDDEN. The ring pointer never leaves its range.
property p_wr_ptr_in_range;
@(posedge clk) disable iff (!rst_n)
(wr_ptr < DEPTH);
endproperty
assert property (p_wr_ptr_in_range)
else $error("history: wr_ptr %0d outside DEPTH", wr_ptr);
// ---- P11. FORBIDDEN. A dropped commit is counted, never silent.
// §14: two commits in one cycle are legal, and a recorder that
// loses one must say so.
property p_second_commit_counted;
@(posedge clk) disable iff (!rst_n)
(commit && commit_2nd && dropped != 16'hFFFF)
|=> (dropped == $past(dropped) + 1);
endproperty
assert property (p_second_commit_counted)
else $error("history: a second commit in one cycle was dropped silently");
// ---- P12. FORBIDDEN. `elapsed` is meaningless while `seen` is
// false, so nothing may read a zero elapsed as "committed last
// cycle" on an unseen resource. §13's reset boundary.
property p_unseen_resource_has_no_elapsed_meaning;
@(posedge clk) disable iff (!rst_n)
(!q_bank_seen) |-> (q_elapsed_bank == 16'd0);
endproperty
assert property (p_unseen_resource_has_no_elapsed_meaning)
else $error("history: an unseen resource reported a non-zero elapsed");
// ---- 27.2 §7: publish every antecedent.
cover property (@(posedge clk) disable iff (!rst_n) commit);
cover property (@(posedge clk) disable iff (!rst_n) commit && commit_2nd);
cover property (@(posedge clk) disable iff (!rst_n) viol);
cover property (@(posedge clk) disable iff (!rst_n) viol && !valid);
cover property (@(posedge clk) disable iff (!rst_n) viol && valid);
cover property (@(posedge clk) disable iff (!rst_n) clear);
cover property (@(posedge clk) disable iff (!rst_n) valid && snap_misclassified);
cover property (@(posedge clk) disable iff (!rst_n) valid && snap_shortfall == 16'd1);
cover property (@(posedge clk) disable iff (!rst_n) valid && snap_shortfall > 16'd1);
cover property (@(posedge clk) disable iff (!rst_n) wrapped != 16'd0);
cover property (@(posedge clk) disable iff (!rst_n) dropped != 16'd0);
cover property (@(posedge clk) disable iff (!rst_n) viol_count == 16'hFFFF);
cover property (@(posedge clk) disable iff (!rst_n) !q_bank_seen);
cover property (@(posedge clk) disable iff (!rst_n) $countones(viol_rule_mask) > 1);
endmodule18. DV — Testing the Reconstruction
// SIMULATION-ONLY. Independent reference: it stores commits in a
// QUEUE and answers "previous on this resource" by SEARCHING BACKWARD,
// rather than maintaining per-resource registers incrementally. A
// different algorithm, so agreement is evidence -- 27.4 §15's argument.
class history_reference;
typedef struct {
int cyc; int cmd; int bank; int grp; bit col;
} rec_t;
rec_t log[$];
function void commit(int cyc, int cmd, int bank, int grp, bit col);
rec_t r; r.cyc = cyc; r.cmd = cmd; r.bank = bank; r.grp = grp; r.col = col;
log.push_back(r);
endfunction
// §5: the previous COLUMN command in the same bank GROUP -- not the
// previous command. Returns -1 when none exists, so "never" is a
// distinct answer from "cycle 0".
function int prev_col_in_group(int grp, int before_cyc);
for (int i = log.size() - 1; i >= 0; i--)
if (log[i].col && log[i].grp == grp && log[i].cyc < before_cyc)
return log[i].cyc;
return -1;
endfunction
function int prev_in_bank(int bank, int before_cyc);
for (int i = log.size() - 1; i >= 0; i--)
if (log[i].bank == bank && log[i].cyc < before_cyc) return log[i].cyc;
return -1;
endfunction
// §4: earliest legal time is a MAXIMUM over applicable rules, and
// the binding rule is the one that produced it. Returns both, so a
// test can assert the report named the binding rule and not the
// first-evaluated one.
function void earliest_legal(int this_cyc, int bank, int grp,
int t_same_bank, int t_ccd_l, int t_ccd_s,
output int earliest, output int binding);
int cands[$]; int rules[$]; int pb, pg;
earliest = 0; binding = -1;
pb = prev_in_bank(bank, this_cyc);
if (pb >= 0) begin cands.push_back(pb + t_same_bank); rules.push_back(0); end
pg = prev_col_in_group(grp, this_cyc);
if (pg >= 0) begin cands.push_back(pg + t_ccd_l); rules.push_back(1); end
foreach (cands[i])
if (cands[i] > earliest) begin earliest = cands[i]; binding = rules[i]; end
endfunction
// §9's convention, written where a reader can see it: legal when the
// commit cycle is at or after the earliest legal cycle.
function bit legal(int this_cyc, int earliest);
return this_cyc >= earliest;
endfunction
endclass| Check | What it establishes |
|---|---|
| Replay §5's six-commit stream; query previous-column-in-group 0 before 4181 | Returns 4177, not 4179 — §5's distinction |
| Same stream, query previous-in-bank 3 | Returns −1 — bank 3 was never committed to |
| Compare the reference's per-resource answers against §16's registers over 50,000 random commits | Backward search and incremental registers agree |
Commit to a bank, then idle past ELAPS_MAX | Saturating elapsed stays at max; no wrap — §13 |
| Reset, then commit to a previously-used bank | seen is false; no violation against a phantom predecessor |
| Two commits in one cycle | dropped increments; P11 |
Fill the ring DEPTH + 3 times | wrapped = 1; wr_ptr in range; P10 |
Violation with observed ≥ required | P6 fires |
| Violation with an empty rule mask | P7 fires |
| Two rules failing on one commit | $countones(mask) > 1 cover hits; binding rule is the maximum — §4 |
| §6's misclassified pair | snap_misclassified set; prev_grp == cur_grp |
| Inject 70,000 violations | viol_count saturates at 0xFFFF; P9 |
| Every rule violated at least once | rules_ever_violated is all ones; monotone per P8 |
| Run with the scheduler disconnected | All 12 properties pass; all 14 covers empty |
The last two rows produce the pair of reports worth publishing:
TWO PASSING TIMING-DEBUG REPORTS
(A) the snapshot was never wired to a running scheduler
p_first_violation_sticky PASS
p_valid_sticky PASS
p_capture_is_first PASS
p_violation_is_a_shortfall PASS
p_violation_names_a_rule PASS
... all 12 properties PASS
------------------------------------------------
cover commit 0 hits
cover viol 0 hits
cover valid && snap_misclassified 0 hits
... all 14 covers 0 hits
eight of the twelve properties are implications and never
armed. The four INVARIANTS -- shortfall arithmetic on an
invalid snapshot, pointer range, count saturation, unseen
elapsed -- also pass, correctly, on a block at reset.
a boot log reading "timing: no violations" is produced by
this run AND by a clean one.
(B) the recorder was wired to `candidate` instead of `commit`
stimulus : full traffic; the history binding uses the
scheduler's candidate-valid signal
all 12 properties PASS
all 14 covers HIT
viol_count 1,284
snap_misclassified 0
------------------------------------------------
snap_prev_cmd / snap_prev_cycle refer to a command that
never appeared on the bus.
every property passes and every antecedent armed, so the
instrument looks healthy. And §1 says a scheduler evaluates
several candidates per cycle and commits at most one, so the
recorder holds commands that were considered and rejected.
Every reconstruction built on it names a wrong previous
command, which reads as a device timing problem.
diagnosis : (A) is 27.2's vacuity argument -- the covers are the
only thing separating a working snapshot from an unbound one.
(B) is §1, and NO property can catch it: the signal is
well-formed, the arithmetic is consistent, and only the
binding is wrong.
the fix : (A) read the covers. (B) cross-check the recorder's
total_commits against a bus-level count of commands actually
driven. A recorder on `candidate` reports several times more
commits than the bus carried, and that ratio is the detector.Report (B)'s last line is the chapter's most useful single check. total_commits from the recorder against a count of commands actually driven on the bus distinguishes a commit-wired recorder from a candidate-wired one in one query — and §1's defect is otherwise undetectable by any property.
19. Corner Cases
| Case | Behaviour | Why |
|---|---|---|
BANKS = 1 or GROUPS = 1 | Elaboration fails | $clog2(1) is 0; index vectors would be [-1:0] |
| 15 banks across 4 groups | Elaboration fails | the organisation is inconsistent |
DEPTH = 1 | Elaboration fails | a ring of one cannot hold a predecessor |
| First command after reset | seen false; no comparison | §13 — a phantom predecessor would fire |
Resource idle past ELAPS_MAX | Elapsed saturates | §13 — a wrap would compute a small gap |
| Two commits in one cycle | Second recorded as dropped | §14 — legal per 25.2 |
| Ring fills | Oldest overwritten, wrapped increments | §16 — the loss is published |
| Two rules fail on one commit | Mask has two bits; binding rule is the max | §4 — order-independent |
Violation with observed ≥ required | P6 fires | the evaluator and snapshot disagree |
| Empty rule mask on a violation | P7 fires | §3 field 6 — it localises nothing |
| Same bank, classifier says different | snap_misclassified set | §6 — the one derived bit |
| Same group, classifier says different | snap_misclassified set | §6 |
| 70,000 violations | Count saturates at 0xFFFF | §15 — honest, imprecise |
| Second violation after the first | First snapshot retained | P1, P2 |
tCCD_L never applied to any pair | rules_ever_applied_in bit stays 0 | §14 row two — unimplemented rule |
Recorder wired to candidate | All properties pass | §1 — only a commit-count cross-check finds it |
| Every shortfall equals 1 | Boundary convention hypothesis | §9, §10 rounding row |
Rows sixteen and seventeen are the two findings that no assertion produces. One is an architectural binding error and the other is a distribution over many violations — and both are why this chapter's instruments publish counts and masks rather than only a pass/fail.
20. Misconceptions
“A timing violation means the device is too slow.” §2, §8. It means a rule was evaluated and failed. Nine causes produce that, and misclassification and conversion errors are cheaper to exclude than margin.
“The previous command is the previous command.” §5. For every scope except device-wide, the rule is about the previous command on the same resource, and a dense interleaved stream makes those two different commands.
“Report the rule that failed.” §4. Earliest legal time is a maximum, so several rules can fail at once and the one reported may not be the binding one. Record a mask.
“Timing legality is about when the request arrived.” §1. It is about commits. Chapter 17.1 owns the commit point as the single instant architectural state may change.
“A candidate is close enough to a commit.” §1, report (B). A scheduler evaluates several candidates per cycle and commits at most one, so a candidate-wired recorder holds commands that never issued — and every property still passes.
“It only violates at high frequency, so it is a signal-integrity problem.” §8's callout, §12. A spacing rule fires on cycle counting. Conversion error, wrong frequency bin, and dual-term crossover are all digital and all cheaper to test.
“Round the parameter to the nearest clock.” §10. A minimum must round up. floor produces a one-cycle shortfall that appears only where the division is inexact.
“Add a cycle to every spacing to be safe.” The module's third law. It removes the symptom, costs bandwidth everywhere, and leaves a misclassification in place to reappear on the next configuration.
“A spurious violation right after reset can be ignored.” §13. It is evidence that the history was not cleared, which means every later reconstruction consulted the same stale table.
“A bounded history buffer is a logic analyser.” §16's callout. It holds DEPTH commits, overwrites the oldest, and cannot see the data bus or anything analog.
“Zero violations means the rule set is right.” §14 row one. A scope that is too wide produces zero violations and a bandwidth shortfall nobody investigates.
21. Interview Reasoning
What is the minimum information that localises a timing violation? Six fields: the violating command, the previous command on the shared resource, both commands' resource identity, the required separation, the observed separation, and the named rule.
Which event does timing legality use? The commit — the instant the command is driven. Not the request, not a candidate, not a grant.
Why does that distinction matter for debugging? A scheduler evaluates several candidates per cycle and commits at most one. A history recorder on the candidate signal holds commands that never issued, and every reconstruction built on it names a wrong predecessor.
How would you detect that mistake? Compare the recorder's commit count against a count of commands actually driven on the bus. A candidate-wired recorder reports several times more.
Your checker reports tCCD_S violated by 2. What else do you ask? What the maximum was. Earliest legal time is a maximum over every applicable rule, so a longer rule may have been violated by more and gone unreported.
Every violation is short by exactly one cycle. What does that suggest? A shared boundary convention — cycles between counted against a parameter specified as issue-to-issue — or a minimum rounded down instead of up. One cause, not many.
Violations appear only at the top frequency. Rank your hypotheses. Unit conversion, since the error scales; the wrong frequency bin's table; a max(nCK, ns) parameter implemented with only the count term; and last, physical margin — because a spacing rule is a cycle count.
Why must a minimum time round up? Because rounding down produces a separation shorter than the device requires. The bug is invisible wherever the division is exact, which is why it appears at some frequencies only.
A violation fires on the first command after reset. What is your hypothesis? The per-resource history was not cleared, so the command was compared against a timestamp from before the reset. Clearing the elapsed values is not enough — the seen flags must clear too.
Zero violations and the bandwidth is 30% low. Where do you look? At scope that is too wide: a group-dependent rule applied as though every pair shared a group, or a rule set written for a different organisation. Nothing fails, so nothing reports it.
22. Exercises
-
§1 lists four events. For each, construct the violation report a recorder wired to it would produce for §5's command stream, and say which reports are internally consistent.
-
§4 shows three rules applying to one commit. Derive the report a checker produces if it evaluates rules in registration order and stops at the first failure, then state the worst understatement that ordering permits.
-
§9 gives three readings of a spacing sentence. For each, write the timestamp comparison and the countdown load value, then say which pairs agree.
-
§11's chain has six links. For each, give an observation that would implicate that link and no other, or argue that no such observation exists.
-
§12's dual-term parameter binds on the count term at low frequency and the time term at high. Derive the exact clock period at which they cross for
max(4 nCK, 7.5 ns), and say what a count-only implementation's violation shortfall is as a function of period. -
§16 publishes
dropped. Construct the reconstruction error a single dropped commit causes, and bound how far forward in the trace its effect can persist. -
Report (B) passes every property. Design one additional property that would catch a candidate-wired recorder, or prove that no property over the recorder's own interface can.
-
A colleague adds one cycle to every spacing parameter and the violations stop. Identify every hypothesis in §8's table still standing, and state the single measurement that would have distinguished them before the change.
23. Where This Goes
A timing violation names its own evidence, and the investigation is a reconstruction. Legality is measured between commits and nothing else; the previous command must be the previous command on the shared resource; earliest legal time is a maximum so several rules can fail at once; and the required separation arrives through a six-link provenance chain whose rounding direction is not symmetric.
Four results carry forward. A shortfall of exactly one, on every rule, is a convention — not nine parameter bugs. A frequency-dependent spacing violation has three digital explanations before any physical one. A recorder wired to candidate satisfies every property and corrupts every reconstruction, detectable only by cross-checking its commit count against the bus. And zero violations with a bandwidth shortfall is a scope-too-wide signature nobody investigates.
Two things stay open. Whether the rule that fired was the right rule is not assertable — §15's one derived bit exposes the common misclassification, and a rule set with no concept of a resource cannot be caught by any per-violation record. And required arrives as an input, so every property in §17 is satisfied by a perfectly recorded violation of a wrongly converted parameter; §11 is a procedure, not a checker.
Chapter 28.3 takes the next family, and its structure is different again. A spacing rule is a relation between two commands, so a violation is local. A refresh obligation is a rate, and a rate has no single violating command — the device requires a certain amount of refresh service over a window, Module 15 owns the scheduling flexibility that makes when negotiable, and the debugging question splits three ways: whether an obligation was actually missed, whether the scheduler merely served it late, or whether something else entirely is correlating with refresh and being blamed on it.
Continue learning
Related tutorials
- Related topic
Calibration Failures
Five failures hide behind one done flag — never started, never converged, never committed, no longer valid, and valid but insufficient. One of them has no digital evidence at all.
- Related topic
Row Misses
Nothing is ever missing in DRAM — every row is always in the array. A miss means the bank holds nothing, which needs exactly one command, and how it came to be closed is information most models discard.
- Related topic
Why Timing Parameters Exist
A DDR command can be perfectly meaningful and target a bank in exactly the right state and still be illegal right now. That third refusal is what timing parameters are, and it is a different question from the first two.
- Related topic
Device Physics Behind Timing
The array's processes take absolute time and know nothing about any clock. A controller counts cycles. Converting between them is where a physical duration becomes a digital obligation — and where the same device needs more cycles the faster you run it.
Standards & specifications
- Governing standard
- JEDEC JESD79 (DDR SDRAM)(opens JEDEC Solid State Technology Association in a new tab)
Defines the DDR SDRAM device itself — signals, command encoding, mode registers, timing parameters and the initialisation sequence — one document per generation. Memory-controller microarchitecture, address-mapping policy, PHY training algorithms and board-level design are not specified by it.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the DDR curriculum.
