Skip to content
VLSI Mentor

DDR · Module 14

tFAW — Four-Activate Window

A rolling window that no countdown can express. Legality depends on a history of several prior events, not one — a density limit needing a timestamp queue and a different class of proof.

Seven parameters, seven pairwise obligations. Each relates a candidate command to one prior event, so each is expressible as a single countdown — load a magnitude, decrement, legal at zero.

tFAW is not like that, and the difference is not a matter of degree:

tFAW limits how many activates may occur in any window. Legality depends on a history of several prior events, not on one.

Chapter 13.3 §2 named this class and set it aside: class D, the rolling-window rate limit, the one that is different in kind. This is the chapter where it arrives, and the first thing it costs is the countdown timer. No countdown can answer “how many activates happened in the last window,” because a counter holds one number and the question is about a set.

1. The Obligation — and Why Five Parts Are Not Enough

Every previous chapter opened with the five-part form. Here it breaks, and the way it breaks is informative.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  PARAMETER          tFAW

  1 triggering event   ??? — see below
  2 constrained event  an ACTIVATE, to any bank
  3 resource           SHARED across the rank
  4 magnitude          a WINDOW length, plus a COUNT limit
  5 sense              ROLLING-WINDOW RATE LIMIT

Part 1 has no single answer. There is no one triggering event, because the obligation is not created by an event — it is created by the accumulation of events. Ask “which activate armed this obligation?” and the honest answer is the oldest one still inside the window, which changes as time passes without any new event occurring.

Part 4 has two components, not one. A window length and a count limit. Neither alone means anything: a window of 30 cycles is not a constraint until you say how many activates it permits, and a limit of four is not a constraint until you say over what interval.

The follow-up questions:

What re-arms it? Every accepted ACTIVATE adds to the history. Nothing re-arms it in the countdown sense, because there is no countdown to reload.

What can dominate it? tRRD, tRP and tRC all constrain an ACTIVATE too. For activate-heavy traffic tFAW is frequently the controlling one, which is why it is rated Advanced and why Chapter 14.7 §11 listed “tFAW ignored” as a debugging candidate.

Cycle or absolute-time component? See §4 — I am explicit about what I could not verify.

2. Density Is Not Spacing

Put tFAW beside tRRD, because the contrast is the fastest way to see what a rolling window is.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  tRRD  — PAIRWISE SPACING
    ACT ──── at least D ────▶ next ACT
    One prior event. One countdown. Legal when it expires.

  tFAW  — ROLLING-WINDOW DENSITY
    │◀────────── any window of W cycles ──────────▶│
    │        at most N activates inside            │
    Several prior events. A history. Legal when the
    oldest counted event leaves the window.

These are not the same constraint with different numbers. Consider a concrete case with an educational window of 8 cycles permitting 4 activates, and a tRRD of 2:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  ACT at cycles 0, 2, 4, 6      → every PAIR is 2 apart, so tRRD is
                                  satisfied at every step
                                → but four activates now sit inside
                                  the window, so a fifth is illegal
                                  until the one at cycle 0 ages out

tRRD is satisfied everywhere and tFAW is violated. The two obligations are independent: you can satisfy any amount of pairwise spacing and still exceed a density limit, because spacing constrains consecutive pairs and density constrains totals.

And the reverse:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  ACT at cycles 0, 1           → the pair is 1 apart, violating tRRD
                               → only two activates in the window, so
                                 tFAW is perfectly satisfied

3. What Ageing Out Means

The mechanism deserves its own section because it has no analogue in the seven previous chapters.

In a pairwise obligation, permission returns because a counter reaches zero — something the design did, in response to an event it saw.

In a rolling window, permission returns because an old event stops counting. Nothing happens. No command is issued, no counter is reloaded. The window simply slides forward until the oldest counted activate falls out of the back of it, and at that instant capacity reappears.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    window W = 8, limit N = 4

    cycle  0  1  2  3  4  5  6  7  8
           A  A  A  A  ·  ·  ·  ·  ?
           └─────────────────────┘
           │◀──── window at cycle 7 ────▶│   4 inside → 5th ILLEGAL

    cycle  0  1  2  3  4  5  6  7  8
           A  A  A  A  ·  ·  ·  ·  A
              └──────────────────────┘
              │◀── window at cycle 8 ──▶│   the cycle-0 activate has
                                           aged out → 3 inside → LEGAL

The boundary. An activate at cycle t counts at cycle now while now - t < W, and ages out when now - t >= W. So the window spans W cycles including the counted activate's own cycle, and the earliest legal cycle for the next activate is:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  earliest legal  =  t_oldest + W

where t_oldest is the cycle of the oldest of the N most recent activates. With activates at 0, 1, 2, 3 and a window of 8, the fifth is legal at cycle 80 + 8.

That is the same N + D shape as every other parameter in the module (Chapter 13.2 §6), which is reassuring: the boundary convention did not have to change, only the question of which event to measure from.

4. What Is Verified

How a rolling-window activate limit differs from a pairwise spacing limit. Four activate commands are accepted in quick succession, each one pushing its timestamp into a history queue. A fifth activate candidate is offered while all four timestamps still lie inside the window, and the window tracker refuses it because the count limit is reached, naming the oldest entry's age as the reason. No command is issued and no counter is reloaded, yet the passage of time causes the oldest entry to age out of the window, which retires it from the queue and returns capacity. The same fifth activate is then accepted. The diagram emphasises that permission returns from an event nobody caused, which is what distinguishes a density limit from a spacing limit.ControllerHistory queueWindow trackerVerdict4 x ACTIVATE — pusheach4 entries insidewindow5th ACTIVATE?no — window fulloldest ages out —retire3 entries inside5th ACTIVATE legalnow

5. RTL — A Timestamp History, Not a Counter

Collision check. Chapter 4.5's bank_group_spacer holds pairwise group-scoped separations. Chapter 14.7's resolver supplies its magnitudes. Chapter 13.3's deadline_scoreboard holds several pairwise obligations as timestamps and maximises them. Chapter 2.3's refresh_deadline_tracker tracks a recurring deadline with an owed count.

None of them holds a history of events. deadline_scoreboard comes closest — it uses timestamps — but it stores one deadline per obligation, and a rolling window needs one timestamp per event. That is a different data structure for a different question, and it is the first genuinely new state shape in this module.

The engineering problem. Answer, every cycle, whether another ACTIVATE would exceed the count limit inside the window — and when the window is full, report when capacity returns, which requires knowing the oldest counted event's age.

Classification: controller-side sequential timing bookkeeping over an event history. Counts cycles, stores cycle numbers, compares them. No physical modelling; in particular no current modelling, despite §4's verified motivation — the controller honours a published limit and models no electrical behaviour whatever.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────
//  activate_window_history
//
//  CLASSIFICATION
//    Controller-side sequential timing bookkeeping over an EVENT
//    HISTORY. Stores cycle numbers and compares them.
//
//  WHAT IT MODELS
//    §1's rolling-window obligation:
//      counted events = ACTIVATE accepted, ANY bank in the rank
//      limit          = MAX_ACTS events
//      window         = WINDOW cycles
//      constrained    = the next ACTIVATE, any bank
//      scope          = SHARED across the rank
//      sense          = ROLLING-WINDOW RATE LIMIT  (class D)
//
//  WHAT IT DOES NOT MODEL
//    Any physical process. §4 verified that this limit exists to bound
//    CURRENT, and this block models no current, no power, no charge and
//    no electrical behaviour of any kind. It honours a published count
//    over a published window. A block claiming to model supply current
//    would be a fiction -- the controller has no visibility into it.
//
//  WHY A QUEUE AND NOT A COUNTER
//    A counter holds one number. The question "how many activates
//    happened in the last WINDOW cycles" is about a SET, and answering
//    it requires knowing WHEN each happened. Storing MAX_ACTS
//    timestamps is the minimum state that answers it: we never need to
//    know about more than MAX_ACTS events, because once that many are
//    inside the window the answer is "full" regardless of what came
//    earlier.
//    That bound is the useful insight: storage is proportional to the
//    COUNT LIMIT, not to the WINDOW LENGTH. A window of 10,000 cycles
//    permitting 4 events needs 4 timestamps, not 10,000 bits of
//    history. Chapter 13.3 §2's table said this; here it is load-
//    bearing.
//
//  CONVENTION (Chapter 13.2 §6, adapted per §3)
//    An ACTIVATE at cycle t COUNTS at cycle now while now - t < WINDOW,
//    and AGES OUT when now - t >= WINDOW. The window therefore spans
//    WINDOW cycles including the counted activate's own cycle, and
//    earliest_legal = t_oldest + WINDOW.
//
//  WRAPAROUND
//    `now` is free-running and WILL wrap, so ages are computed as
//    SIGNED DIFFERENCES exactly as Chapter 13.3 §7 established:
//        aged_out  <=>  $signed(now - ts) >= WINDOW
//    valid while the true age never exceeds half the counter range,
//    which the g_horizon guard enforces.
//
//  SIMULTANEITY (specified, asserted in §7, tested in §9)
//    retire and push on the same cycle -> BOTH happen. The retire frees
//        a slot and the push fills it. This is the common case at a
//        sustained activate rate and must not be serialised.
//    push while full                   -> REFUSED and reported on
//        overrun. The block advises; it never silently drops an event,
//        because a dropped event makes the history wrong for WINDOW
//        cycles afterwards.
//    reset mid-window                  -> history cleared. Correct: a
//        reset controller has no history, and retaining stale
//        timestamps would forbid activates for no reason.
// ─────────────────────────────────────────────────────────────────────
module activate_window_history #(
  // The count limit. §4 verified four for DDR4; parameterised because
  // §4 also says I could not verify that four is universal.
  parameter int MAX_ACTS = 4,
  // Window length in cycles, resolved upstream from whatever unit form
  // the device publishes (§4 does not assert one).
  parameter int WINDOW   = 32,
  // Free-running cycle-counter width.
  parameter int TS_W     = 10,
  // Index into the history. Guarded so MAX_ACTS == 1 stays legal.
  parameter int PTR_W    = (MAX_ACTS <= 1) ? 1 : $clog2(MAX_ACTS),
  // Occupancy COUNT, so it must represent MAX_ACTS itself: sized
  // $clog2(MAX_ACTS+1), not $clog2(MAX_ACTS).
  parameter int OCC_W    = (MAX_ACTS <= 1) ? 1 : $clog2(MAX_ACTS + 1)
) (
  input  logic              clk,
  input  logic              rst_n,

  // ── A counted event: an ACTIVATE accepted, any bank.
  input  logic              act_accepted,

  // ── Verdict for a candidate ACTIVATE. One member of the applicable
  //    set; tRRD, tRP and tRC are the others (Chapters 14.7, 14.2,
  //    14.4), and a consumer takes the maximum. Hence the name.
  output logic              window_has_room,

  // ── How many counted events are inside the window right now.
  output logic [OCC_W-1:0]  occupancy,

  // ── When capacity returns, expressed as cycles remaining. Zero when
  //    there is room. THIS is the output that makes the block useful
  //    to a scheduler: "full" is not actionable, "full for 6 more
  //    cycles" is.
  output logic [TS_W-1:0]   cycles_until_room,

  // ── The oldest counted event's timestamp and age, published for
  //    §10's diagnostics. A violation report naming the oldest event
  //    is a report a human can act on.
  output logic [TS_W-1:0]   oldest_ts,
  output logic              oldest_valid,

  // ── The free-running counter, published so consumers and checkers
  //    share one notion of "now".
  output logic [TS_W-1:0]   now,

  // ── An ACTIVATE was accepted while the window was full. REPORTED,
  //    never absorbed: dropping it would corrupt the history for a
  //    whole window afterwards.
  output logic              overrun
);

  // ── Elaboration guards.
  if (MAX_ACTS < 1) begin : g_max_acts
    initial $fatal(1, "activate_window_history: MAX_ACTS must be >= 1");
  end
  if (WINDOW < 1) begin : g_window
    initial $fatal(1, "activate_window_history: WINDOW must be >= 1");
  end
  if (TS_W < 2) begin : g_ts_w
    initial $fatal(1, "activate_window_history: TS_W must be >= 2");
  end
  // ── THE guard that makes the signed age comparison sound. The
  //    largest age we ever compare against is WINDOW, so the counter
  //    must have more than twice that range for the signed difference
  //    to be unambiguous. Enforced rather than documented, because a
  //    wrap bug surfaces only after 2**TS_W cycles of uptime --
  //    Chapter 13.3 §11's Symptom B.
  if (WINDOW >= (1 << (TS_W - 1))) begin : g_horizon
    initial $fatal(1, "activate_window_history: need WINDOW < 2**(TS_W-1)");
  end

  // ── The history. MAX_ACTS timestamps in a circular buffer, plus
  //    head, tail and occupancy. Storage is proportional to the COUNT
  //    LIMIT, not the window length -- see the header.
  logic [TS_W-1:0] ts   [MAX_ACTS];
  logic [PTR_W-1:0] head, tail;
  logic [OCC_W-1:0] occ;

  // ── Combinational view.
  logic             retiring;
  logic [OCC_W-1:0] occ_eff;
  logic [TS_W-1:0]  oldest_age;

  always_comb begin
    oldest_valid = (occ != '0);
    oldest_ts    = ts[tail];

    // Age of the oldest counted event, as a WRAP-TOLERANT signed
    // difference. Meaningful only when oldest_valid.
    oldest_age = oldest_valid ? (now - ts[tail]) : '0;

    // The oldest entry ages out when its age reaches WINDOW.
    retiring = oldest_valid
            && ($signed(now - ts[tail]) >= $signed(TS_W'(WINDOW)));

    // Effective occupancy accounts for a retire happening THIS cycle,
    // so a retire and a push can coincide. Serialising them would cap
    // the sustained activate rate one below the specification's.
    occ_eff = retiring ? (occ - OCC_W'(1)) : occ;

    window_has_room = (occ_eff < OCC_W'(MAX_ACTS));
    occupancy       = occ_eff;

    // Cycles until capacity returns. When there is room, zero. When
    // full, the oldest entry needs (WINDOW - its age) more cycles.
    if (window_has_room) begin
      cycles_until_room = '0;
    end else begin
      cycles_until_room = TS_W'(WINDOW) - oldest_age;
    end

    overrun = act_accepted && !window_has_room;
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      now  <= '0;
      head <= '0;
      tail <= '0;
      occ  <= '0;
      for (int unsigned i = 0; i < MAX_ACTS; i++) begin
        ts[i] <= '0;
      end
    end else begin
      now <= now + 1'b1;

      // Retire the aged-out oldest entry, and push a new event, in the
      // same cycle if both apply. The occupancy update is written ONCE
      // from both conditions so the two cannot disagree.
      if (retiring) begin
        tail <= tail + 1'b1;
      end

      if (act_accepted && window_has_room) begin
        ts[head] <= now;
        head     <= head + 1'b1;
      end

      // Single occupancy update covering all four combinations.
      case ({retiring, (act_accepted && window_has_room)})
        2'b10:   occ <= occ - OCC_W'(1);
        2'b01:   occ <= occ + OCC_W'(1);
        default: occ <= occ;          // 2'b00 and 2'b11 both no-change
      endcase
    end
  end

endmodule

Interface contract. window_has_room covers only this obligation; tRRD, tRP and tRC also constrain an ACTIVATE, and a consumer takes the maximum — the naming discipline from Chapter 14.4 §6. cycles_until_room is the output that makes this block useful rather than merely correct: a scheduler told “full” can only retry, while one told “full for six more cycles” can go and do something else.

Parameter contract, and the insight worth extracting. MAX_ACTS sets the storage; WINDOW sets the timing. They are independent, and the asymmetry is the useful part: storage is proportional to the count limit, not to the window length. A window of ten thousand cycles permitting four events needs four timestamps. This surprises people — the instinct is that a longer window needs more history — and it falls out of the observation that once MAX_ACTS events are inside, nothing earlier can change the answer.

OCC_W is sized $clog2(MAX_ACTS + 1) because occupancy can equal MAX_ACTS, while PTR_W is $clog2(MAX_ACTS) because an index cannot. Two different sizings, ten characters apart, for the same parameter — which is Chapter 13.1 §14's fifth exercise in a real block.

Wraparound. g_horizon requires WINDOW < 2**(TS_W-1), which bounds every age comparison below half the counter range and makes the signed difference sound. Chapter 13.3 §11 covered why this must be an elaboration guard rather than a comment: the alternative is a bug with an arithmetic period measured in seconds of uptime.

Why the occupancy update is a single case. All four combinations of retire and push are handled in one statement, so the two paths cannot disagree about occ. Writing occ <= occ - 1 inside the retire branch and occ <= occ + 1 inside the push branch would leave the simultaneous case dependent on statement order — and the simultaneous case is the common one at a sustained activate rate, not an edge case.

Corner cases. MAX_ACTS == 1: degenerates to a pairwise spacing of WINDOW, which is a genuinely useful sanity configuration — it should behave exactly like tRRD with that magnitude, and §9's fifth test checks it. Fewer banks than the count limit: a device with 2 banks cannot have 4 activates outstanding on distinct rows, but it can still activate the same bank repeatedly over time, so the window can still fill — the limit counts events, not banks. occ at MAX_ACTS with the oldest not yet aged: cycles_until_room reports the exact remaining wait. Retire and push together: both occur, occ unchanged. act_accepted while full: refused, overrun reported, history untouched.

Synthesis implications. MAX_ACTS timestamp registers, two small pointers, one occupancy counter, one subtractor and one comparator. For four 10-bit timestamps that is about 45 flops — trivially cheap, and worth noting because the “just lengthen tRRD” shortcut of §2 is sometimes justified on area grounds.

Failure modes. Implementing it as a counter of activates reset every WINDOW cycles — a tumbling window rather than a rolling one — which permits up to 2 × MAX_ACTS activates straddling a boundary and is the most common wrong implementation; §7's P1 catches it. Serialising retire and push: caps the sustained rate one activate below the specification. Unsigned age comparison: correct for 2**TS_W cycles then wrong for nearly a full counter period. Sizing OCC_W as $clog2(MAX_ACTS): occupancy of 4 truncates to 0 and the window never appears full. Dropping an over-limit activate silently: history wrong for a whole window afterwards.

6. Why the Tumbling Window Is Wrong

This deserves its own section, because it is the implementation almost everyone writes first.

The tempting simplification: count activates, and every WINDOW cycles reset the count to zero.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  ROLLING (correct)          TUMBLING (wrong)
  ─────────────────          ────────────────
  every window of W          fixed windows, reset at
  is checked                 boundaries

  cycle  0 ....... 8 ....... 16
         AAAA      AAAA
         └──┘      └──┘
         4 in W    4 in W     ← tumbling says both windows are fine

         cycles 6,7,8,9 contain  A A A A  from the two groups
         └───────── 4 in a window of 8 ─────────┘
         plus the earlier ones → a rolling window of 8 spanning
         cycles 5..12 can contain up to EIGHT activates

A tumbling window permits up to twice the limit, by allowing a full quota at the end of one window and another full quota at the start of the next. The specification says any window of W, and a straddling window is one of those.

7. Four Assertions Worth Writing

A density limit needs a different kind of proof from a spacing limit, and P1 is where that shows.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── P1. THE rolling-window property, stated directly: at no time may
//    more than MAX_ACTS counted events lie within the last WINDOW
//    cycles. Written over the STORED HISTORY rather than over the
//    implementation's occupancy counter, so it checks the property and
//    not the bookkeeping.
//    This is the property that catches §6's tumbling-window bug, which
//    no occupancy-based check can catch because a tumbling design's
//    own counter is always within limits.
property p_window_never_over_limit;
  @(posedge clk) disable iff (!rst_n)
    ( count_in_window(now) <= MAX_ACTS );
endproperty
a_window_never_over_limit: assert property (p_window_never_over_limit);

// ── The counting function. A function, not a loop inside the
//    property: a procedural for-loop is ILLEGAL inside a property, and
//    a function call in the property expression is legal and unrolls
//    at elaboration.
function automatic int count_in_window(logic [TS_W-1:0] t);
  int n;
  begin
    n = 0;
    for (int unsigned i = 0; i < MAX_ACTS; i++) begin
      // An entry counts if it is occupied and its age is under WINDOW.
      // Occupancy is determined by position relative to tail and occ,
      // which is why this walks the buffer rather than trusting occ.
      if (slot_occupied(i) && ($signed(t - ts[i]) < $signed(TS_W'(WINDOW))))
        n = n + 1;
    end
    count_in_window = n;
  end
endfunction

// ── P2. Capacity returns EXACTLY when the oldest entry ages out, in
//    both directions. Catches an off-by-one in the ageing boundary,
//    which §3 fixed at "ages out when age >= WINDOW".
property p_room_exactly_at_boundary;
  @(posedge clk) disable iff (!rst_n)
    oldest_valid |->
      ( window_has_room ==
          ( (occ < OCC_W'(MAX_ACTS))
         || ($signed(now - oldest_ts) >= $signed(TS_W'(WINDOW))) ) );
endproperty
a_room_exactly_at_boundary: assert property (p_room_exactly_at_boundary);

// ── P3. A retire and a push on the same cycle BOTH happen. The
//    documented simultaneity rule, asserted because serialising them
//    caps the sustained activate rate one below the specification --
//    a safe-and-slow failure that no functional test reports.
property p_retire_and_push_coexist;
  @(posedge clk) disable iff (!rst_n)
    ( $past(retiring, 1) && $past(act_accepted && window_has_room, 1) )
      |-> (occupancy == $past(occupancy, 1));
endproperty
a_retire_and_push_coexist: assert property (p_retire_and_push_coexist);

// ── P4. No event is ever silently dropped: an accepted ACTIVATE
//    either enters the history or is reported as an overrun.
//    Catches the "absorb it quietly" shortcut, which corrupts the
//    history for a full WINDOW afterwards.
property p_no_silent_drop;
  @(posedge clk) disable iff (!rst_n)
    act_accepted |-> (window_has_room || overrun);
endproperty
a_no_silent_drop: assert property (p_no_silent_drop);

What P1 proves, and why it is the centre of the chapter. It states the rolling property as the specification states it — over any window, counting the stored history — rather than checking that the design's own occupancy counter stays in range. That distinction is the whole point: a tumbling-window design's occupancy counter is always within limits, because it resets. Only a property that counts events by their timestamps can see that eight of them lie within some window of W.

This is the Chapter 13.4 §8 independence principle applied inside an assertion: do not check the bookkeeping, check the property the bookkeeping is supposed to maintain.

What these do not prove. That WINDOW and MAX_ACTS are the right values — §4 says I could not verify them, so this is a datasheet obligation no simulation discharges. That the other activate obligations were consulted: a design using only this block would violate tRRD on back-to-back activates, since four in immediate succession satisfies a count limit of four and violates any pairwise spacing. And nothing about act_accepted reflecting genuine acceptance.

Vacuity. P2 requires oldest_valid and P3 requires a simultaneous retire and push — which random traffic at a low activate rate will never produce. Cover the full window, cover the simultaneous case, and cover a wrap of the timestamp counter. §9 explains how to reach the last one.

8. The Window Filling and Ageing Out

activate_window_history — capacity returns with no command issued

10 cycles
Ten cycles with a window of eight and a limit of four. Activates are accepted at cycles zero, one, two and three, so the occupancy rises to four by cycle four and the window is full. The oldest entry's timestamp is cycle zero and its age rises across the trace. From cycle four to cycle seven the window has no room, and the cycles-until-room output counts down from four to one, telling a scheduler exactly how long the wait is. At cycle eight the oldest entry's age reaches eight, which equals the window, so it ages out and is retired, occupancy drops and the window has room again — with no command having been issued and no counter reloaded. A fifth activate is accepted at that cycle, and because a retire and a push coincide the occupancy stays at four.fillingfillingfull — no roomfull — no roomcapacity returnedcapacity returned4 in window — full4 in window — fullfull, 2 more cycles to waitfull, 2 more cycles to waitage reaches 8 — aged outage reaches 8 — aged outCKnow0123456789act_acceptedoccupancy0123444433oldest_ts0000000001oldest age0123456788has_roomuntil_room0000432100retiringt0t1t2t3t4t5t6t7t8t9

Three observations, each a §7 property.

Cycle 8 is the chapter. has_room rises, and nothing happened to cause it. No command was issued, no counter was reloaded. The oldest entry's age reached 8, which equals the window, so it stopped counting. That is what distinguishes a density limit from a spacing limit, and it is why §3 needed a section of its own.

until_room counting 4, 3, 2, 1 is the output a scheduler can use. “Full” only permits retrying; “full for two more cycles” permits going away and doing something else — which is Module 17's decision, made possible by this block publishing the number.

Occupancy stays at 4 across cycle 8, because a retire and a push coincide. §7's P3 asserts exactly this, and a design that serialised them would show 3 at cycle 8 and cap its own sustained activate rate.

9. Verification — Reaching the Cases That Matter

Test the boundary in both directions. An activate at t_oldest + WINDOW - 1 must be refused and one at t_oldest + WINDOW accepted. §3 fixes the boundary; a one-sided test passes on a design whose window is too short.

Construct the tumbling-window counterexample deliberately. §6's bug is invisible to random traffic at a moderate rate. Issue MAX_ACTS activates as late as possible in one nominal window and MAX_ACTS more as early as possible in the next, then check P1. A correct design refuses the second group; a tumbling design accepts it. This is the single most valuable directed test in the chapter, and no amount of random stimulus reliably produces it.

Force the simultaneous retire and push. Sustain activates at exactly the rate the window permits — one every WINDOW / MAX_ACTS cycles — and the retire and push coincide continuously. P3 then gets real coverage, and a serialising design shows a sustained rate one activate short.

Shrink the counter to reach the wrap. Chapter 13.3 §10's technique, and it applies unchanged: set TS_W small — 5 or 6 bits, keeping WINDOW < 2**(TS_W-1) per the guard — and run thousands of cycles. The counter wraps constantly, and an unsigned age comparison fails almost immediately. The correctness argument is width-independent, so a small-width proof is a proof at all widths.

Test MAX_ACTS == 1 against a pairwise spacer. This is the sanity check that catches structural errors: with a limit of one, the block should behave identically to a pairwise minimum separation of WINDOW. Instantiate it alongside a simple countdown and compare every cycle. Any disagreement is a bug in the queue logic, exposed in the simplest possible configuration.

Test fewer banks than the count limit. A 2-bank configuration cannot hold 4 distinct open rows, and the window can still fill — because the limit counts events, not banks, and the same bank can be activated repeatedly over time. A design that tried to infer occupancy from open-bank state rather than from event history fails here.

10. DV — Independent Reconstruction, and the Report

Invert the representation. The design holds a circular buffer with head, tail and occupancy pointers. A checker should hold a plain unbounded list of every observed activate timestamp and count, on demand, how many fall within the window. That is wasteful and completely independent — it shares no pointer arithmetic with the design, so it cannot reproduce a pointer bug. In a simulation, waste is free.

Count, do not track. The design maintains occupancy incrementally; the checker should recompute from scratch every cycle by scanning its list. An incremental checker can drift the same way the design drifts.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  TIMING VIOLATION
    command        : ACTIVATE, bank 6
    issued at      : cycle 207
    resource        : SHARED — rank-wide activate window
    violated        : tFAW
    window          : 32 cycles, limit 4 activates
    activates in window at issue:
                      cycle 178  (age 29)   ← oldest, still counting
                      cycle 186  (age 21)
                      cycle 195  (age 12)
                      cycle 201  (age  6)
    count           : 4  — at limit
    legal from      : cycle 210   (= 178 + 32)
    short by        : 3 cycles
    note            : every consecutive pair satisfies tRRD; this is a
                      density violation, not a spacing violation

The enumerated list is what makes this report different from every other in the module, and it is not decoration. A pairwise violation has one source event to name. A density violation has MAX_ACTS of them, and which one matters is the oldest — because that is the one whose ageing returns capacity. A report that named only “tFAW violated” would leave an engineer to reconstruct four timestamps from a waveform.

The closing note pre-empts the wrong diagnosis. An engineer seeing an activate violation reaches for tRRD first, because that is the activate spacing parameter. Stating that every pair satisfies tRRD redirects to the density limit in one line.

11. Debugging

Symptom. Activate violations, or activate throughput below model on miss-heavy traffic.

Candidate mechanismEvidenceDiscriminator
Tumbling window instead of rollingViolations cluster near a fixed periodicity unrelated to the workload§6. The periodicity is the fingerprint: it matches WINDOW, and its phase depends on reset timing. Up to 2 × MAX_ACTS activates in one rolling window.
Implemented as a longer tRRDNo violations; activate bursts impossible; throughput short on bursty traffic§2's substitution. Check whether MAX_ACTS back-to-back activates are ever accepted. If not, the burst allowance was given away.
Retire and push serialisedNo violations; sustained activate rate exactly one short§7's P3. Measure sustained rate against MAX_ACTS / WINDOW.
Occupancy width too narrowWindow never reports fullOCC_W against $clog2(MAX_ACTS+1). At MAX_ACTS of 4, a 2-bit occupancy truncates 4 to 0.
Unsigned age comparisonViolations only after long uptime, with an arithmetic periodChapter 13.3 §11. Period matches 2**TS_W × tCK. §9's shrink technique.
Occupancy inferred from open banksFails when the same bank is activated repeatedly§9's fewer-banks test. The limit counts events, not banks.
tRRD ignored, only tFAW checkedViolations on back-to-back activate pairsChapter 14.7. Four in immediate succession satisfies a count limit of four.

The discriminator unique to this chapter is whether the violation is pairwise or aggregate. Check the consecutive spacings of the activates involved. If every pair is legal and the group is not, it is a density violation — and that immediately rules out tRRD, the parameter an engineer would otherwise reach for first. The converse is equally sharp: a violating pair with only two activates in the window is tRRD, not tFAW.

And the second discriminator is periodicity. A tumbling-window bug produces violations at intervals matching WINDOW, with a phase set by reset timing rather than by the workload. Workload effects do not have arithmetic periods.

Responsible layer. If the window is enforced correctly and activate throughput is still short, tFAW may simply be the binding constraint for that access pattern — which is a real architectural finding, not a bug, and points at Module 18's address mapping (fewer row misses) rather than at the timing logic.

12. Common Misconceptions

“tFAW is just a longer tRRD.” Tempting because both constrain activates and tFAW's window is longer than any pairwise spacing. Why it is wrong: §2 — tRRD relates a candidate to one prior event and tFAW to a history. They are independent: activates at 0, 2, 4, 6 satisfy a tRRD of 2 everywhere and violate a window of 8 with a limit of 4. Consequence: implementing tFAW as a single countdown, which cannot express a density limit. Replacement model: class A against class D (Chapter 13.3 §2). Debugging clue: violations only in groups of four or more, never in pairs.

“Enforce it by spacing activates W / N apart.” Tempting because it is correct, trivially implementable with hardware you already have, and cheaper than a queue. Why it is wrong: it forbids the burst the limit exists to permit. A device allowing four activates in a window explicitly allows four back to back and then a gap. Consequence: the same average activate rate with worse latency for bursty traffic — safe, legal, and giving away the flexibility the specification granted. Replacement model: a density limit permits clustering; a spacing limit forbids it. Debugging clue: MAX_ACTS back-to-back activates are never accepted.

“Count activates and reset the counter every window.” Tempting because it is the obvious way to bound a count over an interval. Why it is wrong: §6 — that is a tumbling window, and a rolling window straddling a reset boundary can contain up to twice the limit. Consequence: intermittent violations whose timing depends on the phase of an internal counter, so the same traffic pattern passes or fails unpredictably. Replacement model: every window of W must be checked, which requires event timestamps. Debugging clue: violations with a periodicity matching WINDOW and a phase set by reset.

“A longer window needs more history.” Tempting because a longer interval intuitively contains more events to remember. Why it is wrong: §5 — storage is proportional to the count limit, not the window length, because once MAX_ACTS events are inside the window nothing earlier can change the answer. Consequence: engineers reject the timestamp-queue approach as expensive when it needs four registers. Replacement model: remember the last MAX_ACTS events, whatever the window. Debugging clue: a design that shifts a WINDOW-bit history vector is doing far more work than necessary.

“The window counts banks, so a device with few banks cannot fill it.” Tempting because four activates suggests four distinct rows in four banks. Why it is wrong: the limit counts events over time. The same bank activated, precharged and reactivated contributes multiple events. Consequence: a design inferring window occupancy from open-bank state under-counts and permits violations. Replacement model: an event history, indexed by time and not by bank. Debugging clue: failures on repetitive single-bank row-miss traffic, which is the pattern that looks least likely to hit a four-activate limit.

“More conservative timing can never create a functional problem.” Tempting because every obligation in this module is a Class A or D minimum, so exceeding it is legal. Why it is wrong in this chapter specifically: the conservative substitution of §2 changes the shape of what is permitted, not just the amount — it removes burst capability. A change that is conservative in magnitude can still be wrong in kind. Consequence: a latency regression on bursty workloads that no timing analysis explains, because nothing is violated. Replacement model: conservatism in a density limit costs flexibility, not just cycles. Debugging clue: worse tail latency with unchanged average throughput.

13. Interview Reasoning

“Why is tFAW not equivalent to one countdown timer?” Because a countdown holds one number and tFAW asks a question about a set: how many activates occurred in the last window. Answering it requires knowing when each happened, so the minimum state is a queue of the last MAX_ACTS timestamps. The good detail to add is the bound: storage is proportional to the count limit, not the window length, because once the limit is inside the window nothing earlier can change the answer — so a ten-thousand-cycle window permitting four events still needs only four registers.

“Give me a sequence that satisfies tRRD and violates tFAW.” With a window of 8, a limit of 4 and a tRRD of 2: activates at cycles 0, 2, 4 and 6. Every consecutive pair is 2 apart so tRRD is satisfied at every step, and four activates now lie within the window, so a fifth is illegal until the one at cycle 0 ages out at cycle 8. And the converse is worth volunteering — activates at 0 and 1 violate tRRD while leaving tFAW entirely satisfied. The two obligations are independent, so neither subsumes the other.

“Why not just enforce it with a longer tRRD?” Because it forbids the burst the limit was written to permit. A device allowing four activates in a window allows them back to back followed by a gap; uniform spacing delivers the same average rate with worse latency for bursty traffic. It is safe, legal, cheap, and it gives away flexibility the specification granted — which makes it a reasonable engineering trade in a cost-constrained design and a bad default.

“A colleague counts activates and resets the count every W cycles. What breaks?” That is a tumbling window, not a rolling one. A rolling window straddling a reset boundary can contain a full quota from the end of one interval plus a full quota from the start of the next — up to twice the limit. The nasty part is the signature: the boundary phase is set by reset timing and has no relation to the workload, so the same traffic violates or does not unpredictably. Catching it needs an assertion that counts stored timestamps rather than one that checks the design's own counter, because a tumbling design's counter is always within limits.

“When does permission return, and what causes it?” When the oldest counted activate ages out — its age reaching the window length. And nothing causes it: no command is issued and no counter reloaded, the window simply slides until that event stops counting. That is the structural difference from every pairwise obligation, where permission returns because a counter the design loaded reaches zero. The practical consequence is that a scheduler wants the remaining wait published, because “full” only permits retrying while “full for six more cycles” permits doing something else.

“How would you find a wraparound bug in the age comparison?” Shrink the timestamp counter in simulation — five or six bits — so it wraps every few dozen cycles, and run thousands of cycles. An unsigned comparison fails almost immediately. The argument that makes this a real proof rather than a convenience is that the correctness condition is width-independent: the signed difference is sound whenever ages stay below half the counter range, which an elaboration guard enforces at every width. So a small-width proof is a proof at all widths.

14. Engineering Exercises

1. Fill and age a window. With a window of 32 cycles and a limit of 4, activates occur at cycles 100, 104, 110 and 118. When is the fifth activate legal, and which event determines it?

Worked: the window is full from cycle 118. The oldest counted event is at 100, so capacity returns when its age reaches 32 — at cycle 132. The fifth activate is legal at 132, determined by the oldest event, not the most recent. Note that the event at 118 is irrelevant to the answer.

2. Continue it. After the fifth activate is accepted at cycle 132, when is the sixth legal?

Worked: the history is now 104, 110, 118, 132. The oldest is 104, so the sixth is legal at 104 + 32 = cycle 136. The queue advances one event at a time, and the answer always depends on the oldest of the last four.

3. Separate the two obligations. With a window of 12, a limit of 4, and a tRRD of 3, classify each sequence as legal, tRRD-violating, tFAW-violating, or both: (a) 0, 3, 6, 9, 12; (b) 0, 1, 2, 3; (c) 0, 4, 8, 12, 16; (d) 0, 3, 6, 9, 11.

Worked: (a) pairs are all 3 apart so tRRD holds; the first four are at 0, 3, 6, 9, and the fifth at 12 needs the oldest (0) to age out at 0 + 12 = 12legal, exactly at the boundary. (b) pairs are 1 apart, tRRD-violating; only four in the window so tFAW is fine. (c) pairs 4 apart, tRRD fine; fifth at 16 needs 0 to age out at 12, and 16 ≥ 12 — legal. (d) pairs are 3, 3, 3, 2 — the last pair violates tRRD; and the fifth at 11 needs 0 to age out at 12, so it also violates tFAWboth.

4. Price the substitution. A design enforces the window by spacing activates W / N apart. With a window of 32 and a limit of 4, what is the enforced spacing? Compare the time to complete four activates against the rolling implementation, assuming a tRRD of 4.

Worked: the substitute spacing is 32 / 4 = 8 cycles, so four activates complete at cycles 0, 8, 16, 24 — 24 cycles. The rolling implementation, bounded only by tRRD of 4, completes them at 0, 4, 8, 12 — 12 cycles, half the time. Both then wait for the window before the fifth. The burst allowance is worth 12 cycles of latency on every group of four, and the substitute gives it away for free.

5. Build the tumbling counterexample. With a window of 8 and a limit of 4, construct the activate sequence that a tumbling implementation accepts and a rolling one refuses, and state the maximum number of activates a rolling window of 8 contains under the tumbling design.

Worked: suppose the tumbling counter resets at cycles 0, 8, 16. Issue activates at 5, 6, 7 and then 8, 9, 10, 11 — wait, that is 3 then 4. Cleanly: activates at 4, 5, 6, 7 fill the first nominal window; the counter resets at 8; activates at 8, 9, 10, 11 fill the second. The tumbling design accepts all eight. The rolling window spanning cycles 4 through 11 contains eight activates — twice the limit. A correct design refuses the fifth at cycle 8, since the oldest (cycle 4) does not age out until cycle 12.

6. Size the state. For a limit of 4 and a window of 10,000 cycles, give the timestamp width required, the number of timestamp registers, and the occupancy-counter width. Then say what a WINDOW-bit shift-register implementation would cost.

Worked: g_horizon needs WINDOW < 2**(TS_W-1), so 2**(TS_W-1) > 10000 gives TS_W >= 15. That is 4 registers of 15 bits = 60 flops, plus OCC_W = $clog2(5) = 3 bits. A shift-register history would be 10,000 flops — over 150 times more, for the same answer. This is §12's fourth misconception in numbers.

7. Write the independent checker. Sketch the checker of §10 — an unbounded list, recounted every cycle — and say precisely which class of design bug it can catch that an incremental checker cannot.

15. Summary

tFAW is the module's only rolling-window obligation — Chapter 13.3 §2's class D — and it is different in kind rather than in degree. It limits how many activates may occur in any window, so legality depends on a history of several prior events rather than on one.

That breaks the five-part form the other seven chapters share: there is no single triggering event, because the obligation arises from accumulation rather than from an event, and the magnitude has two components, a window length and a count limit.

Verified: the constraint is genuinely a rolling window permitting at most four activates, and it exists to bound current — which explains why it is a density limit, since current draw is an accumulation. Not verified, and therefore not published here: the window's value at any speed grade, its unit form, whether it depends on page size as tRRD's absolute term verifiably does, or whether DDR5 changes the count or scope.

Permission returns from an event nobody causes. The oldest counted activate ages out when its age reaches the window, and capacity reappears with no command issued and no counter reloaded. The boundary is the module's familiar shape — earliest legal = t_oldest + WINDOW — so only the question of which event to measure from changed.

No countdown can express it, and two wrong implementations are worth recognising on sight. Enforcing it as a longer pairwise spacing is safe, legal and forbids the burst the limit exists to permit — worth 12 cycles per group of four in §14's arithmetic. Enforcing it as a tumbling counter reset every window permits up to twice the limit across a boundary, with a phase set by reset timing, so the same traffic violates unpredictably.

activate_window_history holds MAX_ACTS timestamps in a circular buffer with a wrap-tolerant signed age comparison, retires and pushes in the same cycle, and publishes cycles_until_room — because “full” only permits retrying while “full for six more cycles” permits doing something else. Its most useful structural insight is that storage is proportional to the count limit, not the window length: four registers serve a ten-thousand-cycle window, where a shift-register history would need ten thousand.

And the chapter's key verification lesson: P1 counts stored timestamps rather than checking the design's occupancy counter, because a tumbling-window design's own counter is always within limits. Check the property, not the bookkeeping.

16. What Comes Next

Eight parameters, and every one has been a minimum separation or a density limit — obligations that say not before, with no upper bound, leaving a scheduler free to defer.

Chapter 14.9 is the first that is not. CL is a fixed, programmed latency: a two-sided commitment where the data appears at the configured offset whether or not anybody is ready, and being late is as much a failure as being early. Chapter 14.1 §4 verified the evidence for that distinction in the datasheet itself — Micron's DDR4 speed-bin tables quote tRCD, tRP and tRC in nanoseconds and CL as a bare cycle count, because one kind is a physical requirement and the other is a configured pipeline depth.

That table heading has been waiting eight chapters to be cashed in.

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.