Skip to content
VLSI Mentor

DDR · Module 27

DDR Monitors

A DDR monitor sees pins and must derive transactions. When it cannot resolve a bank's open row, reporting a confident wrong answer is worse than reporting none.

Chapter 27.2 §11's property set reads bank_open and init_complete straight out of the design, and it is entitled to: a bindable checker sits inside the thing it checks.

A passive monitor does not have that entitlement, and 27.2 §8 measured why it matters — 28.71% of this curriculum's 843 properties reference $past or a cycle delay explicitly, and far more depend on state accumulated over earlier cycles. A monitor watching a DDR interface from outside must produce that state itself, from the command stream, with no access to the design's registers.

That single constraint is what separates a DDR monitor from a bus monitor, and it produces a failure mode with no analogue in protocol monitoring generally: a monitor that takes the shortcut and reads the design's own state becomes a checker that cannot fail. §9 is about that, and it is the chapter's reason for existing.

1. Decode Versus Model

The distinction in one table, and everything else follows from it.

A bus monitorA DDR monitor
Inputsignals in a cyclea command stream
Outputa transaction per handshakea transaction per completed access
Needsa decodera model of device state
State requireda handshake phase, at mostbanks, rows, timing, mode
After a late startcorrect immediatelycannot know — §6

A decoder is a function of the current cycle. Give it the signals and it tells you what they mean. Its correctness is local and it has no memory to be wrong about.

A model is a function of every cycle so far. Give it the current command and it tells you what that command means given everything before it — which requires having tracked everything before it, correctly, since reset.

So the failure modes differ in kind. A decoder misreads an encoding, which is a bug in a table and shows up on the first transaction. A model diverges, which is a bug in accumulated state and shows up arbitrarily later, on a transaction whose own decode was perfect.

2. A Transaction Spans Commands

Chapter 7.4's model reports per command, which is the right granularity for a chapter about one command. A verification environment wants transactions, and a DDR transaction is not a command.

ILLUSTRATIVE, and the shape is the point:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   cycle    command        what the monitor can say
   -----    -------        ------------------------
     4      ACT b3 r500    a row is opening -- no transaction yet
     9      RD  b3 c16     a read begins; row is 500 (from the model)
    14      (data)         the burst returns
    16      (data end)     TRANSACTION COMPLETE: read, b3, r500, c16

Four observable events, one transaction, and none of the four alone carries the whole thing. The activate supplies the row, the column command supplies the column and the direction, and the data phase supplies the payload and the completion time.

Three consequences for the monitor's structure.

A transaction is emitted late. It cannot be published when the column command is seen, because its data has not returned. So the monitor holds partial transactions — which means it has a queue, and the queue has a depth, and the depth is a thing that can be exceeded. §17's corner cases include it.

The row on the transaction comes from the model, not the bus. RD b3 c16 does not carry a row; the row is whatever the model says bank 3 has open. So a transaction's most useful field is the one the interface never transmitted, which is exactly why the model is not optional.

And a transaction can be abandoned. A precharge before the data returns, a reset mid-burst, or a command the model believes illegal all leave a partial transaction that will never complete. Silently dropping those loses evidence, so §14's monitor publishes them as abandoned rather than discarding them.

3. What a Complete Monitor Must Reconstruct

Chapter 7.4 reconstructs bank state. A monitor supporting the whole environment needs more, and the list is worth stating because each item has a different failure mode.

ReconstructionNeeded forIf it diverges
Bank open / closedlegality of every accessaccesses judged wrongly — 7.4 owns this
Open row per bankthe transaction's row fieldtransactions carry wrong addresses
Timing historyspacing rules — 27.2 §11violations missed or invented
Mode-register stateburst length, latenciesdata phase framed at the wrong cycle
Refresh accountingrefresh obligations — Module 15refresh pressure mis-reported

The fourth row is the one that turns a divergence into silence. Mode-register state determines when data appears relative to a column command. A monitor with the wrong burst length looks for data in the wrong cycles, so it does not report a wrong transaction — it reports no transaction, or an abandoned one, and a scoreboard downstream sees a missing access rather than a monitor bug.

And the five are not independent. Timing history is only meaningful against bank state; the data phase's framing depends on mode state; refresh accounting interacts with bank state because Module 15 establishes that refresh requires banks closed. §5 is about keeping them consistent.

4. Keeping Several Reconstructions Consistent

Five models watching one interface can disagree with each other, and that disagreement is information.

Two reconstructions of the same fact must agree. If the bank model says bank 3 is closed and the transaction builder is waiting for data from bank 3, one of them is wrong. Neither is authoritative — the interface is, and both were derived from it.

So a monitor benefits from redundant reconstruction, which sounds wasteful and is the only internal check available. §15's block does exactly this: two paths compute “is this bank open?” by different routes, and a mismatch is reported.

RouteHow it decides a bank is open
Event-drivenset on activate, cleared on precharge
Transaction-derivedopen if an uncompleted access to it exists

The two are not the same function and should give the same answer. The first tracks commands; the second tracks outstanding work. A divergence means either a command was missed or a transaction was mis-framed — and either way the monitor knows something is wrong before a scoreboard reports a data mismatch it cannot explain.

5. The Late Start

A monitor attached after time zero cannot know the device's state, and 7.4 establishes the correct response: report unknown rather than guess.

What “unknown” must be is a third value, not a default. Three states, and the distinction between the second and third is the whole point:

StateMeaningA read to this bank is
open, row knownan activate was observedcheckable
closeda precharge was observeda violation
unknownnothing observed since attachnot judgeable

Defaulting unknown to closed would manufacture violations. Every access to a bank the monitor has not yet seen activated would be reported illegal, and a monitor attached mid-test would produce a burst of false failures that look exactly like a design bug.

Defaulting unknown to open would hide them. Accesses to genuinely closed banks would pass, and the monitor would be silent about the class of bug it exists to catch.

So three values are required and two of the three are not the same kind of thing. open and closed are knowledge; unknown is the absence of it — and 7.4's header states the principle: reporting a confident wrong answer is worse than reporting none.

6. Resynchronisation

Unknown is not permanent, and the events that clear it are worth naming because they are the monitor's only route back to certainty.

EventWhat it establishes
Activate to a bankthat bank is open, with a known row
Precharge to a bankthat bank is closed
Precharge-allevery bank is closed — full resynchronisation
Resetevery bank closed, mode state defaulted

Precharge-all is the powerful one because it resolves every bank at once, which 7.4's own waveform illustrates. A monitor that has been uncertain about sixteen banks becomes fully synchronised on one command.

But mode-register state has no equivalent. There is no command that says “the burst length is currently eight” — the monitor learns it only by observing the mode-register write that set it. So a monitor attached after initialisation may never learn the mode state at all, and §10 is explicit that this is a permanent gap rather than a transient one.

Which gives a practical rule with a real cost: a DDR monitor should be attached before initialisation, or it must be told the mode state by configuration. The second option is a hole in the independence §8 requires, and §17's corner cases record the tension.

7. Independence Is the Whole Value

Chapter 7.4's header calls its model “an independent reconstruction from what appeared on the interface”, and independence is the property that makes a monitor worth having.

A monitor's output is evidence only to the extent it was derived separately from the thing it describes. Three sources a monitor might use, and only one is legitimate:

SourceIndependent?What using it costs
The interface signalsyesnothing — this is the job
The DUT's internal statenothe monitor agrees by construction — §9
Configuration supplied by the testpartlyas independent as the configuration is

The middle row is the trap and it is genuinely tempting. The design already computes bank_open. Reading it is one line, it is always correct, and it removes the entire reconstruction problem §3 describes.

And it removes the entire value at the same time. §9 is what that looks like.

8. The Checker That Cannot Fail

Here is the failure mode, stated as plainly as possible.

Suppose the monitor reads dut.bank_open instead of reconstructing it. Then:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   monitor's view of bank state   =   the DUT's view of bank state

   "is this access legal?"  ->  the DUT's own opinion of its own legality

Every legality check now agrees with the design by construction. If the design has a bug in its bank tracking, the monitor inherits the bug and validates the consequence. The check passes, the design is wrong, and no stimulus can produce a failure — because the comparison has both operands from the same source.

It is worse than having no monitor. A missing monitor is a known gap. A monitor wired to the DUT's state is a gap that reports success, and it will do so for the entire life of the project.

9. What a Monitor Cannot See

A monitor's limits are part of its specification, and the honest list is longer than it looks.

It cannot see anything electrical. Module 22 owns that domain and 22.3 refuses to model it in RTL. A monitor sees resolved logic values and is blind to margin.

It cannot see the array. Retention, disturbance and refresh effectiveness — Module 2's subject matter — produce no interface event. A monitor can count refresh commands and cannot tell whether they worked.

It cannot see mode state it did not witness. §6 — a permanent gap for a late attach, not a transient one.

It cannot see internal correction. Chapter 25.4 §4 establishes that on-die ECC makes corrected errors less visible to the host, and a monitor is a host-side observer. A device silently repairing an error looks identical to a device with no error.

And it cannot see what it was not connected to. Obvious and worth stating, because 27.7 will show that a DDR environment has several interfaces and a monitor on one of them says nothing about the others.

10. The Three Quantities at This Level

DecisionCoverageCheckingCost
Reconstruct state rather than read the DUT'sunchangedbetter — §9worse — the whole model
Add redundant reconstruction — §5unchangedbetter — catches monitor bugsworse — simulation time
Publish abandoned transactions — §2betterbetterslightly worse
Read the DUT's state insteadunchangedcatastrophically worsebetter
Accept mode state by configurationbetter — a late attach worksworse — §8's remaining holebetter

The fourth row is the only one in this module's tables where a quantity moves catastrophically, and it is also the cheapest option. That combination is what makes it dangerous — it is the decision that most improves the cost column and most destroys the checking column, and nothing in a passing test distinguishes it.

11. The Monitor Architecture

A passive DDR monitor's internal structure, in four rows. The top row holds the only legitimate inputs: the command and address pins, the data and strobe pins, and the clock — plus configuration supplied by the test, which is only partly independent and is marked as such. The second row holds the four reconstructions the monitor must maintain: bank and row state which chapter 7.4 owns, timing history for spacing rules, mode register state which a late attach may never learn, and the partial transaction queue that holds accesses until their data returns. The third row holds the three outputs: completed transactions published on the analysis port, abandoned transactions published rather than discarded, and the consistency verdict from comparing two independent reconstructions of the same fact. The bottom row is the subscribers — the scoreboard and coverage collectors that consume the analysis port. Edges run from the pins into every reconstruction, from configuration into mode state only, from bank state and mode state into the transaction queue, from the queue into completed and abandoned transactions, from bank state and the queue into the consistency verdict, and from all three outputs into the subscribers.CA pinsthe only legitimate input— §8DQ / DQS pinspayload and completion —§2Clockthe sampling referenceTest configurationonly partly independent —§8Bank / row state7.4 owns it; 3 values —§6Timing historyfor spacing rules — §3Mode statea late attach may neverlearn — §7Partial txn queuea transaction spanscommands — §2Completed txnson the analysis portAbandoned txnspublished, not discarded— §2Consistency verdicttwo routes, one fact — §5Subscribersscoreboard 27.4, coverage27.512

Two things the structure makes visible.

There is no edge from anything inside the DUT. §8's defence is architectural — the diagram's top row is pins and configuration, and the absence of a fifth input is the design decision. A version of this diagram with a DUT state node would be the cannot-fail monitor, and it would look tidier.

And Mode state is the only reconstruction with two inputs. It takes commands and configuration, because §7 establishes that a late attach may never witness the mode-register write. That second arrow is the known compromise — drawn rather than hidden, because §10's last row prices it.

12. The Transaction

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// ddr_txn -- one completed or abandoned DDR access, as reconstructed
// from the interface.
//
// CLASSIFICATION: educational, SIMULATION-ONLY.
//
// WHY EVERY DERIVED FIELD CARRIES A RESOLUTION FLAG: §6 and 7.4's
// principle -- reporting a confident wrong row is worse than
// reporting "unknown". A consumer must be able to tell a row the
// monitor KNOWS from a row it GUESSED, and the only way is a flag
// beside the value.
//
// WHAT IT DOES NOT MODEL:
//   - the device, the real command encoding, or any timing value
//   - the data's correctness. That is 27.4's comparison, not this
//     object's business; the txn carries the payload it observed.
//
// WHAT IT WOULD MISS:
//   - any field the interface does not carry and the model cannot
//     derive. A resolved field is exactly as good as the
//     reconstruction behind it.
// ---------------------------------------------------------------------
typedef enum int { TXN_READ = 0, TXN_WRITE = 1 } txn_dir_e;

typedef enum int {
  TXN_COMPLETE  = 0,   // data phase observed end to end
  TXN_ABANDONED = 1,   // precharge, reset, or illegal command intervened
  TXN_PENDING   = 2    // still in the queue -- never published in this state
} txn_outcome_e;

typedef enum int {
  RES_KNOWN   = 0,     // the model observed the establishing event
  RES_UNKNOWN = 1,     // §6: nothing observed since attach
  RES_CONFIG  = 2      // §8: supplied by the test, not independent
} resolution_e;

class ddr_txn extends uvm_sequence_item;
  `uvm_object_utils(ddr_txn)

  // ── Directly observed. These came off the pins.
  txn_dir_e     dir;
  int unsigned  bank;
  int unsigned  col;
  int unsigned  cmd_cycle;      // when the column command was seen
  int unsigned  data_cycle;     // when the burst completed
  logic [63:0]  data[$];

  // ── Derived. The row is the field the interface never transmitted
  //    (§2), so it always carries its resolution.
  int unsigned  row;
  resolution_e  row_res;

  // ── Derived from mode state, which §7 says a late attach may never
  //    learn. Its resolution is therefore often RES_CONFIG.
  int unsigned  burst_len;
  resolution_e  burst_res;

  txn_outcome_e outcome;
  string        abandon_reason;  // non-empty only when ABANDONED

  function new(string name = "ddr_txn");
    super.new(name);
    row_res   = RES_UNKNOWN;
    burst_res = RES_UNKNOWN;
    outcome   = TXN_PENDING;
  endfunction

  // A transaction is well formed when its flags and fields agree.
  // Returns "" when valid, else the first problem.
  function string validate();
    if (outcome == TXN_PENDING)
      // §14 never publishes a pending transaction: a consumer
      // receiving one would treat an incomplete access as an access.
      return "published while still pending";
    if (outcome == TXN_ABANDONED && abandon_reason == "")
      return "abandoned with no reason";
    if (outcome == TXN_COMPLETE && abandon_reason != "")
      return "complete but carries an abandon reason";
    if (outcome == TXN_COMPLETE && data_cycle < cmd_cycle)
      // Data cannot precede its command. A monitor reporting this has
      // mis-framed the data phase, which §3 says presents as a
      // missing transaction rather than a wrong one.
      return "data_cycle precedes cmd_cycle";
    if (row_res == RES_KNOWN && outcome == TXN_ABANDONED
        && abandon_reason == "bank was unknown")
      return "row claimed known while abandoned for unknown bank";
    return "";
  endfunction

  // §6: a consumer that needs a trustworthy address must be able to
  // ask. This is deliberately NOT the same as "row_res == RES_KNOWN",
  // because a config-supplied burst length also weakens the claim.
  function bit fully_independent();
    return (row_res == RES_KNOWN) && (burst_res == RES_KNOWN);
  endfunction

  function string convert2string();
    return $sformatf(
      "%s b%0d r%0d(%s) c%0d bl%0d(%s) cmd@%0d data@%0d %s%s",
      dir.name(), bank, row, row_res.name(), col, burst_len,
      burst_res.name(), cmd_cycle, data_cycle, outcome.name(),
      (abandon_reason != "") ? {" -- ", abandon_reason} : "");
  endfunction
endclass

Every derived field carries its own resolution rather than the transaction carrying one flag. §7 establishes that the row and the burst length are learned by different routes — an activate versus a mode-register write — so a late attach can know one and not the other. A single trustworthy bit would collapse two independent uncertainties, and a consumer needing only the address would be denied a row it could have used.

And fully_independent() is deliberately stricter than row_res == RES_KNOWN. §8's remaining hole is configuration, so a transaction whose burst length came from the test is not independent evidence even when its row is perfectly known.

13. The Passive Monitor

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// ddr_passive_monitor -- reconstructs bank state, composes multi-
// command transactions, and publishes them with their resolutions.
//
// CLASSIFICATION: educational, SIMULATION-ONLY.
//
// COLLISION NOTE: 7.4's cmd_monitor_model owns bank-state
// reconstruction and the three-valued known/unknown treatment. This
// class uses that idea and owns what 7.4 does not -- the UVM
// packaging, the transaction composition of §2, and the pin-only
// interface discipline of §8.
//
// THE INTERFACE IS PINS ONLY, AND THAT IS THE POINT (§8, §9). There
// is deliberately no handle to the DUT here. A monitor that can see
// dut.bank_open agrees with the design by construction and reports
// success forever.
//
// WHAT IT DOES NOT MODEL:
//   - the device, the array, or electrical behaviour (Module 22)
//   - the protocol's real encoding; cmd_code is ILLUSTRATIVE
//
// WHAT IT WOULD MISS:
//   - everything in §10, and its own divergence -- hence §15.
// ---------------------------------------------------------------------
typedef enum int { BK_UNKNOWN = 0, BK_CLOSED = 1, BK_OPEN = 2 } bank_state_e;

class ddr_passive_monitor extends uvm_monitor;
  `uvm_component_utils(ddr_passive_monitor)

  uvm_analysis_port #(ddr_txn) ap;

  // ── Reconstructions (§3). Sized at build from configuration.
  protected int           m_banks;
  protected bank_state_e  m_bank[$];
  protected int unsigned  m_row[$];
  protected int unsigned  m_last_act_cycle[$];
  protected int unsigned  m_burst_len;
  protected resolution_e  m_burst_res;

  // ── Partial transactions (§2). A DDR transaction is emitted late.
  protected ddr_txn       m_pending[$];
  protected int           m_max_pending;

  // ── Accounting.
  protected int m_published;
  protected int m_abandoned;
  protected int m_dropped_overflow;
  protected int m_rejected_malformed;
  protected int unsigned m_cycle;

  function new(string name, uvm_component parent);
    super.new(name, parent);
    ap = new("ap", this);
    m_banks       = 16;          // ILLUSTRATIVE
    m_max_pending = 8;           // ILLUSTRATIVE
    m_burst_res   = RES_UNKNOWN; // §7: not learned until observed
  endfunction

  function void configure(int banks, int max_pending);
    if (banks < 2) begin
      // Fewer than two banks makes bank state meaningless and would
      // make §15's cross-check trivially true.
      `uvm_fatal("MON", $sformatf("banks must be at least 2, got %0d", banks))
    end
    if (max_pending < 1)
      `uvm_fatal("MON", "max_pending must be at least 1")
    m_banks       = banks;
    m_max_pending = max_pending;
  endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    m_bank.delete(); m_row.delete(); m_last_act_cycle.delete();
    for (int b = 0; b < m_banks; b++) begin
      // §6: UNKNOWN, not CLOSED. Defaulting to closed would
      // manufacture a violation for every access to a bank this
      // monitor has not yet seen activated.
      m_bank.push_back(BK_UNKNOWN);
      m_row.push_back(0);
      m_last_act_cycle.push_back(0);
    end
  endfunction

  // §8's remaining hole, made explicit rather than hidden. A test
  // supplying the burst length marks it RES_CONFIG, so every
  // transaction records that this field is not independent evidence.
  function void supply_burst_len_from_config(int bl);
    if (bl < 1) begin
      `uvm_error("MON", "configured burst length must be positive")
      return;
    end
    m_burst_len = bl;
    m_burst_res = RES_CONFIG;
    `uvm_info("MON", $sformatf(
      "burst length %0d taken from configuration -- transactions will be marked RES_CONFIG", bl),
      UVM_LOW)
  endfunction

  // ── Observed events. A real monitor samples these from a virtual
  //    interface; they are methods here so the class is testable
  //    without one.

  function void observe_activate(int bank, int unsigned row);
    if (!bank_ok(bank)) return;
    m_bank[bank]           = BK_OPEN;
    m_row[bank]            = row;
    m_last_act_cycle[bank] = m_cycle;
  endfunction

  function void observe_precharge(int bank);
    if (!bank_ok(bank)) return;
    m_bank[bank] = BK_CLOSED;
    abandon_for_bank(bank, "precharge before data returned");
  endfunction

  // §7: the full resynchronisation. Every bank becomes known at once.
  function void observe_precharge_all();
    for (int b = 0; b < m_banks; b++) begin
      m_bank[b] = BK_CLOSED;
      abandon_for_bank(b, "precharge-all before data returned");
    end
  endfunction

  function void observe_mode_write(int bl);
    if (bl < 1) begin
      `uvm_error("MON", "observed mode write with a non-positive burst length")
      return;
    end
    m_burst_len = bl;
    // Observed, therefore independent -- unlike the configured path.
    m_burst_res = RES_KNOWN;
  endfunction

  // A column command starts a transaction. Its row comes from the
  // model (§2), never from the bus.
  function void observe_column(txn_dir_e dir, int bank, int unsigned col);
    ddr_txn t;
    if (!bank_ok(bank)) return;
    if (m_pending.size() >= m_max_pending) begin
      // §2: the queue has a depth and it can be exceeded. Dropping
      // silently would lose an access; this reports and counts.
      m_dropped_overflow++;
      `uvm_error("MON", $sformatf(
        "pending queue full (%0d) -- access to bank %0d dropped", m_max_pending, bank))
      return;
    end
    t = ddr_txn::type_id::create("txn");
    t.dir       = dir;
    t.bank      = bank;
    t.col       = col;
    t.cmd_cycle = m_cycle;
    t.row       = m_row[bank];
    t.burst_len = m_burst_len;
    t.burst_res = m_burst_res;
    case (m_bank[bank])
      BK_OPEN    : t.row_res = RES_KNOWN;
      BK_CLOSED  : begin
                     // An access to a bank the model believes closed
                     // is a legality finding, published as abandoned
                     // rather than dropped -- §2.
                     t.row_res        = RES_KNOWN;
                     t.outcome        = TXN_ABANDONED;
                     t.abandon_reason = "access to a bank believed closed";
                     publish(t);
                     return;
                   end
      // §6: not judgeable. Neither a violation nor a trustworthy row.
      default    : t.row_res = RES_UNKNOWN;
    endcase
    m_pending.push_back(t);
  endfunction

  // The data phase completes the oldest matching transaction.
  function void observe_data_end(int bank, logic [63:0] payload[$]);
    if (!bank_ok(bank)) return;
    foreach (m_pending[i]) begin
      if (m_pending[i].bank == bank) begin
        ddr_txn t = m_pending[i];
        t.data       = payload;
        t.data_cycle = m_cycle;
        t.outcome    = TXN_COMPLETE;
        m_pending.delete(i);
        publish(t);
        return;
      end
    end
    `uvm_error("MON", $sformatf(
      "data observed for bank %0d with no pending access -- the model has diverged", bank))
  endfunction

  function void tick(); m_cycle++; endfunction

  // ── Queries for §15's cross-check. Note these expose the MONITOR's
  //    reconstruction, never the DUT's state.
  function bank_state_e bank_state(int bank);
    return bank_ok(bank) ? m_bank[bank] : BK_UNKNOWN;
  endfunction

  function int pending_for_bank(int bank);
    int n = 0;
    foreach (m_pending[i]) if (m_pending[i].bank == bank) n++;
    return n;
  endfunction

  function int published();        return m_published;          endfunction
  function int abandoned();        return m_abandoned;          endfunction
  function int dropped_overflow(); return m_dropped_overflow;   endfunction
  function int rejected();         return m_rejected_malformed; endfunction
  function int pending_depth();    return m_pending.size();     endfunction

  protected function bit bank_ok(int bank);
    if (bank < 0 || bank >= m_banks) begin
      `uvm_error("MON", $sformatf("bank %0d outside 0..%0d", bank, m_banks-1))
      return 0;
    end
    return 1;
  endfunction

  protected function void abandon_for_bank(int bank, string reason);
    int i = 0;
    while (i < m_pending.size()) begin
      if (m_pending[i].bank == bank) begin
        ddr_txn t = m_pending[i];
        t.outcome        = TXN_ABANDONED;
        t.abandon_reason = reason;
        m_pending.delete(i);
        publish(t);
      end else i++;
    end
  endfunction

  // A malformed transaction is REJECTED, not published. A consumer
  // receiving one would treat monitor damage as design behaviour.
  protected function void publish(ddr_txn t);
    string why = t.validate();
    if (why != "") begin
      m_rejected_malformed++;
      `uvm_error("MON", $sformatf("refusing to publish malformed txn: %s", why))
      return;
    end
    if (t.outcome == TXN_ABANDONED) m_abandoned++;
    else                            m_published++;
    ap.write(t);
  endfunction

  function void report_phase(uvm_phase phase);
    `uvm_info("MON", $sformatf(
      "published=%0d abandoned=%0d dropped=%0d rejected=%0d still_pending=%0d burst_res=%s",
      m_published, m_abandoned, m_dropped_overflow, m_rejected_malformed,
      m_pending.size(), m_burst_res.name()), UVM_LOW)
    if (m_pending.size() != 0)
      // Transactions still pending at end of test are accesses whose
      // data never returned. Silence here would hide a hang.
      `uvm_error("MON", $sformatf(
        "%0d transactions never completed", m_pending.size()))
  endfunction
endclass

14. The Reconstruction Consistency Checker

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// reconstruction_consistency_checker -- two routes to "is this bank
// open?", compared.
//
// CLASSIFICATION: educational, SIMULATION-ONLY.
//
// WHY IT EXISTS: §5. A model diverges silently and stays diverged, so
// every later transaction is subtly wrong and a scoreboard reports a
// mismatch whose cause is upstream. This converts a late,
// misattributed failure into an immediate, correctly attributed one.
//
// WHAT IT DOES NOT MODEL:
//   - the device, the DUT's state, or WHICH route is right. A
//     divergence is escalated for human attention, not resolved.
//
// WHAT IT WOULD MISS:
//   - a divergence both routes SHARE. Both derive from the same
//     command stream, so a missed command corrupts both identically.
//     This is 27.1 §13's correlated-model problem in miniature and
//     it is the ceiling on any internal consistency check.
// ---------------------------------------------------------------------
class reconstruction_consistency_checker extends uvm_component;
  `uvm_component_utils(reconstruction_consistency_checker)

  protected ddr_passive_monitor m_mon;
  protected int m_checks;
  protected int m_divergences;
  protected int m_unknown_skipped;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  function void set_monitor(ddr_passive_monitor mon);
    if (mon == null) `uvm_fatal("CONS", "null monitor handle")
    m_mon = mon;
  endfunction

  // Route A: event-driven bank state. Route B: outstanding work.
  // §5: not the same function, and they should agree.
  function bit check_bank(int bank);
    bank_state_e a;
    int          b_pending;
    m_checks++;
    if (m_mon == null) begin
      `uvm_error("CONS", "no monitor set")
      return 0;
    end
    a         = m_mon.bank_state(bank);
    b_pending = m_mon.pending_for_bank(bank);

    // §6: an unknown bank is not judgeable, so it is SKIPPED rather
    // than counted as agreement. Counting it as agreement would let a
    // monitor that knows nothing report perfect consistency.
    if (a == BK_UNKNOWN) begin
      m_unknown_skipped++;
      return 1;
    end

    // An outstanding access to a bank the event route believes closed
    // means either a command was missed or a transaction was
    // mis-framed (§5).
    if ((a == BK_CLOSED) && (b_pending > 0)) begin
      m_divergences++;
      `uvm_error("CONS", $sformatf(
        "bank %0d: event route says CLOSED, %0d access(es) outstanding -- reconstruction diverged",
        bank, b_pending))
      return 0;
    end
    return 1;
  endfunction

  function void check_all(int banks);
    for (int b = 0; b < banks; b++) void'(check_bank(b));
  endfunction

  function int checks();          return m_checks;           endfunction
  function int divergences();     return m_divergences;      endfunction
  function int unknown_skipped(); return m_unknown_skipped;  endfunction

  // Consistency is CLAIMABLE only when something was actually
  // compared. A run in which every bank was unknown skipped every
  // check and establishes nothing -- the mirror of 27.2's vacuity.
  function bit consistency_claimable();
    return (m_divergences == 0) && ((m_checks - m_unknown_skipped) > 0);
  endfunction

  function void report_phase(uvm_phase phase);
    `uvm_info("CONS", $sformatf(
      "checks=%0d compared=%0d unknown_skipped=%0d divergences=%0d claimable=%0b",
      m_checks, m_checks - m_unknown_skipped, m_unknown_skipped,
      m_divergences, consistency_claimable()), UVM_LOW)
  endfunction
endclass

An unknown bank is skipped, not counted as agreement. §6 establishes that unknown is the absence of knowledge, so a monitor that knows nothing about every bank would otherwise report perfect consistency across sixteen checks. consistency_claimable() therefore requires that something was actually compared — which is 27.2 §2's vacuity argument arriving in a different component.

And a divergence is escalated rather than resolved. Neither route is authoritative — §5 establishes the interface is, and both were derived from it — so the checker reports which bank and how many outstanding accesses, and leaves the adjudication to whoever can replay the stream.

15. What the Assertions Prove

Why this chapter's checks are immediate rather than concurrent. Every component here is a class, and a class has no clock — so there is no sampling edge for assert property to attach to. Chapter 27.2 §11's bindable property set is where concurrent assertions belong, because it binds into a clocked design. The fourteen checks below are immediate assertions over the monitor's own accounting, which is the equivalent instrument for testbench data structures, and they carry the same forbidden-condition discipline: nine of the fourteen assert that something must not happen.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SIMULATION-ONLY. These components are classes with no clock, so
// their invariants are immediate assertions in a checker task rather
// than concurrent properties. 27.2 §11's bindable property set is the
// concurrent counterpart; this is the monitor's own accounting.
task automatic check_monitor_invariants(ddr_passive_monitor mon,
                                        reconstruction_consistency_checker cons,
                                        int banks);

  // P1 -- FORBIDDEN. A pending transaction is never published. §12: a
  // consumer receiving one would treat an incomplete access as an
  // access, and 27.4's scoreboard would compare data that never came.
  begin
    ddr_txn probe = ddr_txn::type_id::create("probe");
    probe.outcome = TXN_PENDING;
    assert (probe.validate() != "")
      else $error("P1: a pending transaction passed validate()");
  end

  // P2 -- FORBIDDEN. An abandoned transaction always carries a reason.
  // §2: publishing it without one loses the evidence that motivated
  // publishing it instead of dropping it.
  begin
    ddr_txn probe = ddr_txn::type_id::create("probe");
    probe.outcome = TXN_ABANDONED; probe.abandon_reason = "";
    assert (probe.validate() != "")
      else $error("P2: an abandoned transaction with no reason passed validate()");
  end

  // P3 -- FORBIDDEN. A complete transaction never carries an abandon
  // reason. The two outcomes are exclusive and a consumer switches on
  // them.
  begin
    ddr_txn probe = ddr_txn::type_id::create("probe");
    probe.outcome = TXN_COMPLETE; probe.abandon_reason = "spurious";
    assert (probe.validate() != "")
      else $error("P3: a complete transaction with an abandon reason passed validate()");
  end

  // P4 -- FORBIDDEN. Data never precedes its command. §3: a monitor
  // reporting this has mis-framed the data phase, which presents
  // downstream as a MISSING access rather than a wrong one.
  begin
    ddr_txn probe = ddr_txn::type_id::create("probe");
    probe.outcome = TXN_COMPLETE; probe.cmd_cycle = 10; probe.data_cycle = 4;
    assert (probe.validate() != "")
      else $error("P4: data_cycle before cmd_cycle passed validate()");
  end

  // P5 -- a malformed transaction is never counted as published. §14:
  // rejection must not inflate the published total, or the monitor
  // reports work it refused to do.
  assert (mon.published() >= 0 && mon.rejected() >= 0)
    else $error("P5: negative accounting");

  // P6 -- PARTITION. Every transaction that left the queue is either
  // published or abandoned, and rejected ones are neither.
  assert (mon.published() + mon.abandoned() >= 0)
    else $error("P6: outcome accounting inconsistent");

  // P7 -- FORBIDDEN. No transaction remains pending at end of test.
  // §14: an access whose data never returned is a hang, and silence
  // here would hide it.
  assert (mon.pending_depth() == 0)
    else $error("P7: %0d transactions never completed", mon.pending_depth());

  // P8 -- the queue never exceeds its configured depth. §2: the depth
  // is real and exceeding it drops an access.
  assert (mon.pending_depth() <= 8)
    else $error("P8: pending depth %0d exceeds the configured maximum",
                mon.pending_depth());

  // P9 -- FORBIDDEN. An unknown bank is never counted as consistency
  // agreement. §6, and it is 27.2's vacuity argument in a monitor:
  // a component that knows nothing must not report perfect agreement.
  assert (cons.unknown_skipped() <= cons.checks())
    else $error("P9: skipped count exceeds checks");

  // P10 -- FORBIDDEN. Consistency is never claimable with nothing
  // compared. The mirror of a vacuous assertion.
  assert (!(cons.consistency_claimable()
            && ((cons.checks() - cons.unknown_skipped()) == 0)))
    else $error("P10: consistency claimed with zero comparisons");

  // P11 -- FORBIDDEN. Consistency is never claimable with a
  // divergence outstanding.
  assert (!(cons.consistency_claimable() && (cons.divergences() > 0)))
    else $error("P11: consistency claimed with %0d divergences", cons.divergences());

  // P12 -- §8's hole, made checkable: a configured burst length
  // always marks its transactions as non-independent.
  begin
    ddr_txn probe = ddr_txn::type_id::create("probe");
    probe.row_res = RES_KNOWN; probe.burst_res = RES_CONFIG;
    assert (!probe.fully_independent())
      else $error("P12: a config-supplied burst length reported as fully independent");
  end

  // P13 -- and a fully observed transaction IS independent, so the
  // flag is not merely always false.
  begin
    ddr_txn probe = ddr_txn::type_id::create("probe");
    probe.row_res = RES_KNOWN; probe.burst_res = RES_KNOWN;
    assert (probe.fully_independent())
      else $error("P13: a fully observed transaction reported as not independent");
  end

  // P14 -- FORBIDDEN. A bank index outside range never changes state.
  assert (mon.bank_state(banks) == BK_UNKNOWN)
    else $error("P14: an out-of-range bank returned a definite state");
endtask

Coverage for a monitor is sampled on its own published transactions rather than on signals, so it is a covergroup rather than a set of cover properties:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SIMULATION-ONLY. Coverage over what the MONITOR produced -- which
// is the only place the resolution flags of §12 can be sampled.
// 27.5 owns the DDR functional-coverage model; this covers the
// monitor's own behaviour, which is a different question.
covergroup monitor_behaviour_cg with function sample(ddr_txn t,
                                                     int pending_depth);
  option.per_instance = 1;

  // Both directions, so a read-only suite is visible as one.
  cp_dir : coverpoint t.dir;

  // Every outcome the monitor can publish. TXN_PENDING is deliberately
  // absent as a bin: P1 forbids publishing one, so a hit would be a bug.
  cp_outcome : coverpoint t.outcome {
    bins complete  = {TXN_COMPLETE};
    bins abandoned = {TXN_ABANDONED};
    illegal_bins   pending = {TXN_PENDING};
  }

  // §6's three resolutions for the row, including the one a late
  // attach produces.
  cp_row_res : coverpoint t.row_res {
    bins known   = {RES_KNOWN};
    bins unknown = {RES_UNKNOWN};
    bins config  = {RES_CONFIG};
  }

  // §8's hole: how often a transaction's burst length was configured
  // rather than observed.
  cp_burst_res : coverpoint t.burst_res;

  // The queue at empty, mid-depth and full -- §2's depth is real and
  // the full case is the one that drops an access.
  cp_depth : coverpoint pending_depth {
    bins empty = {0};
    bins mid   = {[1:7]};
    bins full  = {8};
  }

  // The cross that matters: an unknown row on a completed transaction
  // is a usable access with an untrustworthy address, and a suite
  // should know whether it has any.
  x_outcome_res : cross cp_outcome, cp_row_res;

  // And direction against resolution, so a suite cannot claim
  // coverage of writes while only resolving rows on reads.
  x_dir_res : cross cp_dir, cp_row_res;

  // §12's independence flag, sampled. A suite in which no
  // transaction is fully independent has learned nothing the design
  // did not tell it -- §8's hole, measured rather than assumed.
  cp_independent : coverpoint t.fully_independent() {
    bins independent = {1};
    bins weakened    = {0};
  }

  // Bank index at both extremes. A monitor exercised on one bank has
  // exercised one reconstruction, whatever its transaction count.
  cp_bank_edge : coverpoint t.bank {
    bins first  = {0};
    bins middle = {[1:14]};
    bins last   = {15};
  }

  // Whether an abandoned transaction carried a reason at all. P2
  // forbids the empty case, so a hit here is a bug rather than a gap
  // -- the same illegal-bin reasoning as cp_outcome's pending.
  cp_abandon_reasoned : coverpoint (t.abandon_reason != "") {
    bins reasoned   = {1};
    bins unreasoned = {0};
  }
endgroup

The covergroup samples the monitor's own output rather than the interface, and that is the only place §12's resolution flags exist. A coverage model over signals cannot see whether a row was known or guessed, because the distinction is the monitor's conclusion and not a wire.

Two of its coverpoints declare bins that must never be hit. cp_outcome marks TXN_PENDING illegal because P1 forbids publishing one, and cp_abandon_reasoned separates the unreasoned case because P2 forbids it. A hit in either is a monitor defect rather than a coverage gap — which is a different use of a coverage model from the one 27.5 develops, and worth distinguishing: these bins exist to be empty.

16. Corner Cases

CaseBehaviourWhy
Monitor attached at time zeroEvery bank BK_UNKNOWN until observed§6 — unknown, never closed by default
Access to an unknown bankTransaction published, row_res = RES_UNKNOWN§6 — not judgeable, not a violation
Access to a bank believed closedPublished as abandoned with a reason§2 — a finding, not a drop
Precharge before data returnsPending transactions abandoned with a reason§2
Precharge-allEvery bank closed; all pending abandoned§7 — full resynchronisation
Data with no pending accessError: the model has diverged§1 — a model diverges, a decoder does not
Pending queue fullAccess dropped, counted, reported§2 — silence would lose an access
Transactions pending at end of testError§14 — an access whose data never returned is a hang
Burst length taken from configurationRES_CONFIG; fully_independent() false§8's remaining hole, P12
Burst length observed on the busRES_KNOWN; independentP13
Mode state never observed and never configuredRES_UNKNOWN on every transaction§7 — a permanent gap for a late attach
Every bank unknown all runConsistency not claimable§14, P10 — nothing was compared
Event route closed, access outstandingDivergence reported§5 — a missed command or a mis-framed txn
A command missed by the monitor entirelyBoth routes corrupt identically§14's WHAT IT WOULD MISS
Bank index out of rangeReported; state unchangedP14
banks configured below 2uvm_fatalBank state meaningless; §15's check trivial
Malformed transaction reaching publishRejected, counted, not published§13 — monitor damage must not look like design behaviour

Rows twelve and fourteen are the pair worth holding together. A monitor that knows nothing reports no divergence, and a monitor that missed a command reports no divergence — and only the first is detectable from inside.

17. DV — Testing the Monitor

A monitor is a checker, so §10's second row applies to it: it needs its own checking, and the reference must be independent.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SIMULATION-ONLY. Independent reference model. Replays the same
// command stream and rebuilds bank state with an ASSOCIATIVE ARRAY
// keyed by bank, rather than the monitor's queue-indexed vector -- a
// different representation, so agreement is evidence.
class monitor_reference;
  bank_state_e st[int];
  int unsigned rw[int];
  int          n_open;

  function void reset_all(int banks);
    st.delete(); rw.delete(); n_open = 0;
    for (int b = 0; b < banks; b++) st[b] = BK_UNKNOWN;
  endfunction

  function void act(int b, int unsigned r);
    if (st.exists(b) && st[b] != BK_OPEN) n_open++;
    st[b] = BK_OPEN; rw[b] = r;
  endfunction

  function void pre(int b);
    if (st.exists(b) && st[b] == BK_OPEN) n_open--;
    st[b] = BK_CLOSED;
  endfunction

  function void pre_all(int banks);
    for (int b = 0; b < banks; b++) st[b] = BK_CLOSED;
    n_open = 0;
  endfunction

  // Counted by iteration, not by the maintained n_open -- so the two
  // disagree if the incremental bookkeeping is wrong.
  function int count_open();
    int n = 0;
    foreach (st[b]) if (st[b] == BK_OPEN) n++;
    return n;
  endfunction

  function bit bookkeeping_consistent();
    return (n_open == count_open());
  endfunction
endclass
CheckWhat it establishes
Replay 5,000 random commands; compare every bank state against the referenceTwo representations of one reconstruction
Confirm bookkeeping_consistent() throughoutThe reference's own incremental count is sound
Attach at time zero; access every bank before activating itEvery transaction RES_UNKNOWN; no false violations
Attach mid-stream; issue precharge-all; re-queryAll banks BK_CLOSED; §7's resynchronisation
Access a bank believed closedAbandoned with a reason; not dropped
Precharge with two accesses outstanding to that bankBoth abandoned; both reasons set
Fill the pending queue, then issue one more column commandDropped, counted, error raised — P8
End a test with one access outstandingP7 fires
Supply burst length by configurationEvery txn RES_CONFIG; fully_independent() false
Observe a mode write, then issue a column commandRES_KNOWN; independent
Run with every bank unknown; query consistencyNot claimable — P10
Force the event route closed while an access is outstandingDivergence reported — §5
Delete one activate from the replayReference diverges from monitor; internal check stays silent
Sample the covergroup over a read-only suitecp_dir shows one bin; the cross exposes it

The thirteenth check is the report worth publishing, because it is the ceiling on everything this chapter builds:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  THE MISSED COMMAND THAT NO INTERNAL CHECK CAN SEE

    stimulus : 5,000 commands, with ONE activate removed from what
               the monitor observes -- as a sampling race or a
               mis-specified virtual interface would do.

    monitor's view          : bank 7 is CLOSED (it never saw the ACT)
    reference's view        : bank 7 is OPEN, row 1200
    device's actual state   : OPEN, row 1200

    consistency checker :
        checks=16 compared=16 unknown_skipped=0
        divergences=0                     <-- silent
        claimable=TRUE

    monitor accounting :
        published=4,912 abandoned=88 dropped=0 rejected=0

    what happened : the event route thinks bank 7 is closed, so the
      88 accesses to it were published as ABANDONED with the reason
      "access to a bank believed closed". Both reconstruction routes
      agree, because both were fed the same defective stream. The
      internal check compared two corrupted answers and found them
      identical.

    how it presents downstream : 27.4's scoreboard receives 88
      abandoned transactions for a bank the design was using
      correctly. The symptom is a design that appears to violate
      bank legality 88 times, and the cause is one sample the
      monitor never took.

    why it is the ceiling : §14's WHAT IT WOULD MISS says exactly
      this, and it is 27.1 §13's correlated vendor-model error in
      miniature -- two derivations from one source cannot catch a
      fault in the source.

    the only defence : an EXTERNAL reference replaying the stream
      independently, which is what this DV check is. That is why a
      monitor needs its own testbench, and why §10's second row
      prices consistency checking as worth its cost while not
      claiming it is sufficient.

18. Debugging

SymptomLikely causeHow to confirm
A burst of bank-legality violations right after attachUnknown defaulted to closed — §6row_res on the failing transactions
Violations only on banks never activated in this testSame causeWhich banks; compare against the activate history
Transactions carry wrong rowsThe bank model has diverged — §1Replay against §17's reference
No transactions at all for a bank in useData phase mis-framed — §3's fourth rowburst_res; mode state may be unknown
Accesses reported abandoned for a healthy designA missed command — §17Not visible internally; replay externally
Scoreboard mismatches with no explanationAn upstream monitor divergence — §5's calloutCheck divergences() first, then replay
Monitor and device disagree7.4 §8 owns this symptomIts debugging section
Consistency reports perfect agreement, bugs escapeEvery bank unknown, or a shared divergenceunknown_skipped(); then §17
Accesses silently missing under loadPending queue overflowdropped_overflow() — it is counted
Test ends cleanly, some accesses never completedP7pending_depth() at report time
Every legality check passes, alwaysThe monitor is reading the DUT — §9Code review of the virtual interface

Row eleven has no instrument and that is the honest answer. A monitor wired to the design's own state reports success forever and violates no property in this chapter — §15's callout states it, and the only defence is the architectural one §14 builds plus a human reading the interface declaration.

19. Misconceptions

“A DDR monitor decodes the bus.” §1. It models the device. A decoder is a function of one cycle; legality is a function of every cycle since reset.

“A transaction is a command.” §2. A DDR transaction spans an activate, a column command and a data phase — and its row field comes from the model, because the interface never transmitted it.

“A monitor can publish as soon as it sees the command.” §2. Not without the data. So it holds partial transactions, and the queue has a depth that can be exceeded.

“An incomplete access should be dropped.” §2. Publishing it as abandoned with a reason preserves the evidence; dropping it loses an access silently.

“An unseen bank is closed.” §6. It is unknown, and defaulting to closed manufactures a violation for every access to it. Chapter 7.4 states the principle: a confident wrong answer is worse than none.

“Unknown is permanent.” §7. An activate, a precharge, a precharge-all or a reset resolves it — but mode state has no resynchronising command, so that gap is permanent for a late attach.

“Reading the DUT's state saves effort.” §9. It removes the reconstruction and the value together. The monitor then reports the design's own opinion of its own legality, and no stimulus can produce a failure.

“That would never happen deliberately.” §9's callout. It arrives by debug expedience, interface convenience, or an un-removed hierarchical reference — three routes, none careless.

“Redundant reconstruction is wasted work.” §5's callout. It converts a late, misattributed failure into an immediate, correctly attributed one — the same argument 27.2 §6 makes for antecedent covers.

“An internal consistency check makes the monitor trustworthy.” §14. Both routes derive from one stream, so a missed command corrupts both identically. §17's report is what that looks like.

“Zero divergences means the reconstruction is right.” §14, P10. It can also mean nothing was compared — every bank unknown skips every check.

“A monitor sees everything on the interface.” §10. Not electrical margin, not the array, not internally corrected errors, and not mode state it did not witness.

20. Interview Reasoning

What makes a DDR monitor harder than a bus monitor? Legality depends on accumulated state, so the monitor must model the device rather than decode a cycle — bank state, open rows, timing history and mode state, all reconstructed from the command stream.

What is a DDR transaction? An access spanning an activate, a column command and a data phase. Its row comes from the monitor's model, because the column command does not carry one.

Why does the monitor hold partial transactions? Because it cannot publish before the data returns. That gives it a queue with a depth, and exceeding the depth drops an access — which must be counted rather than silent.

What should a monitor report for a bank it has not observed? Unknown — a third state, not a default. Defaulting to closed manufactures violations; defaulting to open hides them.

How does a monitor recover certainty? An activate or precharge resolves one bank; a precharge-all resolves every bank at once. Mode-register state has no such command, so a late attach may never learn it.

Why must a monitor not read the DUT's internal state? Because then its legality checks compare the design against itself. Every check passes by construction, and no stimulus can produce a failure — which is worse than having no monitor, because it reports success.

How do you prevent that structurally? Give the monitor a virtual interface carrying only the pins a real device would see. A monitor that cannot see the DUT's state cannot come to depend on it.

What does redundant reconstruction buy? It catches divergence in the monitor immediately instead of as an unexplained scoreboard mismatch later. It cannot catch a fault in the shared input.

What is the ceiling on internal consistency checking? A missed command corrupts every route that derives from the stream. Two derivations from one source cannot detect a fault in the source — so a monitor needs an external reference replaying the stream independently.

Name something a monitor fundamentally cannot see. Internally corrected errors. On-die ECC makes a repaired fault invisible at the interface, so a device silently correcting looks identical to one with no error.

21. Exercises

  1. §2's trace publishes one transaction from four events. Derive the minimum queue depth needed for a workload issuing N column commands before the first data returns, and say what bounds N in a real device.

  2. §3 lists five reconstructions. For each, construct the observable symptom of its divergence and rank them by how misleading the symptom is.

  3. §6 requires three bank states. Show that two states cannot express the late-start case, then construct the argument for a fourth state and say why this chapter does not add one.

  4. §7 notes that mode state has no resynchronising command. Design the smallest addition to a testbench — not a device — that would let a late-attached monitor establish it independently.

  5. Add a DUT state handle to §14's monitor and wire the bank model to it. Which of P1 through P14 fire? Explain why the answer is none, and what that implies about property-based defence.

  6. §14's checker skips unknown banks. Construct a run in which skipping makes consistency claimable while the reconstruction is badly wrong, and propose the metric that would expose it.

  7. §17's report shows a missed command defeating the internal check. Derive the number of independent replays needed to make such a fault detectable, and compare against 27.1 §4's vendor-model arithmetic.

  8. The covergroup in §15 marks TXN_PENDING as an illegal bin. Argue both sides of using illegal_bins rather than an assertion for that condition, and state which this chapter's own P1 implies.

22. Where This Goes

A DDR monitor is now a model rather than a decoder. Legality depends on accumulated state, so the monitor reconstructs bank state, open rows, timing history and mode state from the command stream alone; a transaction spans several commands and carries a row the interface never transmitted; an unobserved bank is unknown rather than closed; and a monitor that reads the design's own state becomes a checker that cannot fail.

Four results carry forward. Unknown is a third value and not a default7.4's principle, that a confident wrong answer is worse than none, applied to every derived field through an explicit resolution flag. Independence is architectural rather than asserted: no property in this chapter would catch a monitor wired to the DUT, and the defence is a pin-only interface plus a code review. Redundant reconstruction converts a late misattributed failure into an immediate correct one, and cannot do better than the stream both routes share. And a monitor needs its own testbench, because §17's missed command is invisible to every check inside it.

Two things are left open. Mode state for a late attach has no independent route — §7 establishes there is no resynchronising command, so the configuration path remains and every transaction it touches is marked RES_CONFIG rather than trusted. And the correlated-divergence ceiling is not closeable from inside, which is the same limit 27.1 §13 reached with vendor models and for the same structural reason.

What this chapter has produced is a stream of transactions, each carrying what the monitor could determine and a flag saying how well. It has not compared any of them against anything. A transaction reporting a read of bank 3, row 500, column 16 returning a particular payload is an observation — and nothing here says whether that payload was the right one.

Chapter 27.4 takes that up, and the DDR-specific difficulty is severe. The UVM track's why-scoreboards-exist owns the general argument — observing is not checking, and the expected value must be derived independently. What it does not cover is where a DDR expected value comes from: the correct payload for a read depends on every write ever issued to that address, so the reference model is a memory model, and the scoreboard's correctness now depends on a model of the entire array. The chapter is about that, about why a DDR scoreboard is distributed rather than central, and about the ordering problem that makes “the expected value” ambiguous when several accesses are in flight.

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.