Skip to content
VLSI Mentor

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.

EventWhat it isDoes timing legality use it?
Requestan upstream transaction arrives — 17.5no
Candidatethe scheduler considers an entry this cycle — 17.4no
Grantarbitration selects itno
Commit / issuethe command is driven on the busyes — 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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:

ScopeExample ruleThe pair it governs
Same banktRCD, tRP, tRAS14.1, 14.2two commands to one bank
Same bank grouptCCD_L14.6two column commands in one group
Different bank grouptCCD_S14.6two column commands across groups
Cross-banktRRD_S / tRRD_L14.7two activates, group-dependent
Device-wide rollingtFAW14.8activates 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.

FieldWhere it comes fromFailure mode if wrong
1. Violating commandthe commit that triggered the checka candidate logged as a commit — §1
2. Previous relevant commandthe last commit on the same resourcethe last commit anywhere — §5
3. Resource identitybank, bank group, rank of boththe wrong rule applied — §2
4. Required separationthe parameter, converted — §11off-by-one, wrong unit, wrong generation
5. Actual separationcommit cycle minus commit cycleboundary convention — §10
6. Violated rulethe named parametera 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.

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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 cycles
A ten-cycle command trace in which a read is committed and then a second read is committed four cycles later. The clock runs throughout. The committed command lane shows a read at cycle one and a read at cycle five, with no command in between. The bank lane shows bank two for the first read and bank three for the second. The bank group lane shows group zero for both, so the two commands are in the same bank group and the longer column-spacing rule applies. The classifier output, however, reports different group, which is the defect: it has misclassified the relationship. Because of that misclassification the required separation lane shows two, the short rule, instead of eight, the long rule. The observed separation is four. Against the short rule of two, four is legal and no violation is raised until the checker that knows the true group evaluates it at cycle five, where the violation output rises and stays high. The first marker at cycle one notes the first read committing to bank two in group zero. The second marker at cycle five notes the second read committing to bank three, also group zero, four cycles later. The third marker at cycle five notes that the true requirement is eight, so the observed four is a violation by four cycles. The first phase spans cycles one to four and is the interval during which the long rule was still counting down. The second phase spans cycles five to nine, after the violating commit, during which the violation output remains latched.tCCD_L window, 8 requiredtCCD_L window, 8 requiredviolation latchedviolation latchedRD commits: bank 2, group 0RD commits: bank 2, group 0long rule still countinglong rule still countingRD bank 3 grp 0: needs 8, got 4RD bank 3 grp 0: needs 8,got 4CKcommit_cmd0RDRDRDRDRDRDRDRDRDcommit_bank0222233333commit_grp0000000000clsf_samerequired0000022222observed0000044444violationt0t1t2t3t4t5t6t7t8t9

Read 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

A sequence diagram with five participants showing how a timing violation is reconstructed. The scheduler commits a command, and only the commit is recorded, because a request, a candidate and a grant are all events that timing legality does not use. The commit is sent to the history recorder, which stores it twice: once in a bounded chronological trace for human context, and once in a per-resource table keyed by bank, bank group and rank, because the rule needs the previous command on the same resource rather than simply the previous command. The rule evaluator asks the per-resource table for the previous command matching the scope of each applicable rule, receives that command and its commit cycle, and computes an earliest legal issue time for every applicable rule. It then takes the maximum across all of them, because earliest legal time is a maximum and not a single rule's result. If the actual commit cycle is earlier than that maximum, the evaluator reports a violation to the snapshot block, and it reports the full set of rules that failed rather than only the first one evaluated, so the binding constraint is visible. The snapshot latches the first violation of the run and refuses to overwrite it when later violations arrive, because a cascade of consequent violations must not erase the earliest reliable evidence. Finally the snapshot returns a localized report naming the violating command, the previous command on the shared resource, the resource identity of both, the required separation, the observed separation, and every violated rule.Reconstructing a timing violation from committed commandsSchedulerHistoryPer-resourceRule evalSnapshotcommit only — notcandidatekey by bank / group/ rankprevious on THISresource?command + commitcycleearliest legal = maxover rulesevery failing rule,not the firstfirst violationwins, latchedlocalized six-fieldreport

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.

#CauseSignatureDiscriminating observation
1Off-by-one — boundary conventionviolation is exactly 1 cycleshortfall is always 1, across every instance and every rule
2Wrong parameter valueone rule violated, others cleanthe applied count differs from the converted device figure — §11
3Wrong unit conversionviolations scale with frequencyrecompute the chain; the error is proportional, not constant — §11
4Bank misclassificationsame-bank rule never appliedthe two commands' bank fields are equal and the classifier says otherwise
5Bank-group misclassificationtCCD_L/tRRD_L never appliedas §6 — group fields equal, classifier disagrees
6Stale timing historyviolation after an idle gap or a resetthe previous-command timestamp predates the last reset — §13
7Simultaneous eventsviolation only at back-to-back commitstwo commits in one cycle, or a commit in the cycle a counter reloads — §14
8Candidate counted as commitviolations that reference commands never on the busthe referenced command is absent from a bus-level trace — §1
9Generation mismatcha group-dependent rule applied to a device without groupsthe 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.

ReadingCommands at cycles a and b are legal whenN = 4, a = 10: earliest legal b
Cycles between the commandsb − a − 1 ≥ N15
Cycles from the first to the secondb − a ≥ N14
The second may issue on cycle a + Nb ≥ a + N14

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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 separation

Six links, and each has a characteristic failure.

LinkFailureSignature
Specificationthe figure is a max(nCK, ns) and only one term was takenfrequency-dependent — §12
Software valuethe wrong frequency bin's tableviolation only in one bin
Conversionps treated as ns, or the reciprocal usedwrong by a factor, not by one
Roundinga minimum rounded downshort by exactly one, intermittently
Encodingthe field is offset and the offset was applied twice, or not at allconstant offset across all rules sharing the encoding
Interpretationcommand clocks versus controller clockswrong 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):

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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.

ObservationSupportsDiscriminating experiment
Violation on the first command after resettimestamps not clearedreset with a long idle before the first command; the violation should not move
Violation after a long idlecounter wrapwiden the counter, or saturate it; a saturating counter cannot produce a small gap
Violation only after a frequency changetimestamps in stale unitschange 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.

MismatchConsequenceSignature
Group-dependent rules applied to a device without groupsevery pair classified as same-group; the long rule always appliesno violations, bandwidth well below expectation
Group-independent rules applied to a device with groupstCCD_L never appliedreal violations the controller believes are legal — §6
The wrong number of groups configuredbanks assigned to wrong groupsviolations 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// 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
endmodule

snap_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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// 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
endmodule

seen 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 both27.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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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);
endmodule

18. DV — Testing the Reconstruction

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
CheckWhat it establishes
Replay §5's six-commit stream; query previous-column-in-group 0 before 4181Returns 4177, not 4179 — §5's distinction
Same stream, query previous-in-bank 3Returns −1 — bank 3 was never committed to
Compare the reference's per-resource answers against §16's registers over 50,000 random commitsBackward search and incremental registers agree
Commit to a bank, then idle past ELAPS_MAXSaturating elapsed stays at max; no wrap — §13
Reset, then commit to a previously-used bankseen is false; no violation against a phantom predecessor
Two commits in one cycledropped increments; P11
Fill the ring DEPTH + 3 timeswrapped = 1; wr_ptr in range; P10
Violation with observed ≥ requiredP6 fires
Violation with an empty rule maskP7 fires
Two rules failing on one commit$countones(mask) > 1 cover hits; binding rule is the maximum — §4
§6's misclassified pairsnap_misclassified set; prev_grp == cur_grp
Inject 70,000 violationsviol_count saturates at 0xFFFF; P9
Every rule violated at least oncerules_ever_violated is all ones; monotone per P8
Run with the scheduler disconnectedAll 12 properties pass; all 14 covers empty

The last two rows produce the pair of reports worth publishing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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

CaseBehaviourWhy
BANKS = 1 or GROUPS = 1Elaboration fails$clog2(1) is 0; index vectors would be [-1:0]
15 banks across 4 groupsElaboration failsthe organisation is inconsistent
DEPTH = 1Elaboration failsa ring of one cannot hold a predecessor
First command after resetseen false; no comparison§13 — a phantom predecessor would fire
Resource idle past ELAPS_MAXElapsed saturates§13 — a wrap would compute a small gap
Two commits in one cycleSecond recorded as dropped§14 — legal per 25.2
Ring fillsOldest overwritten, wrapped increments§16 — the loss is published
Two rules fail on one commitMask has two bits; binding rule is the max§4 — order-independent
Violation with observed ≥ requiredP6 firesthe evaluator and snapshot disagree
Empty rule mask on a violationP7 fires§3 field 6 — it localises nothing
Same bank, classifier says differentsnap_misclassified set§6 — the one derived bit
Same group, classifier says differentsnap_misclassified set§6
70,000 violationsCount saturates at 0xFFFF§15 — honest, imprecise
Second violation after the firstFirst snapshot retainedP1, P2
tCCD_L never applied to any pairrules_ever_applied_in bit stays 0§14 row two — unimplemented rule
Recorder wired to candidateAll properties pass§1 — only a commit-count cross-check finds it
Every shortfall equals 1Boundary 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. §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.

  2. §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.

  3. §9 gives three readings of a spacing sentence. For each, write the timestamp comparison and the countdown load value, then say which pairs agree.

  4. §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.

  5. §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.

  6. §16 publishes dropped. Construct the reconstruction error a single dropped commit causes, and bound how far forward in the trace its effect can persist.

  7. 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.

  8. 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

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.