Skip to content
VLSI Mentor

DDR · Module 6

DM — The Data Mask

The data mask lets a write commit only part of a burst. Its polarity is inverted from intuition, in DDR4 it shares a pin with two other functions, and on x4 devices it does not exist — which turns a partial write into read-modify-write.

A burst delivers a fixed number of beats. Chapter 4.4 §5 established that this granularity is not negotiable — the prefetch organisation determines it, and Chapter 4.5 §1 showed it reaching a full cache line.

But a requester does not always want to write all of it.

The data mask is the mechanism, and the chapter's question is:

How does a write commit only part of what it transfers, and what happens when that mechanism is unavailable?

The second half turns out to matter more than the first. In DDR4 the mask shares a pin with two other functions and is mutually exclusive with them, and on x4 devices the pin does not exist at all — which means partial writes become a completely different and far more expensive operation.

1. Per Beat, Per Byte

The mask operates at two granularities simultaneously, and both are needed.

Per byte, because there is one mask line per byte lane. A x8 device has one; a x16 device has two. The byte is the masking granularity — you cannot mask an individual bit.

Per beat, because the mask line is sampled with each beat of the burst. The same wire carries a different decision on every beat.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   burst of 8 beats, x16 device (2 byte lanes)
     -> 8 beats x 2 lanes  =  16 independent mask decisions
     -> carried on 2 wires, sampled 8 times each

That is a lot of control information on very few wires, and it is achieved by the same mechanism as the data: the mask lines are source-synchronous with the data, sampled by DQS alongside the beats they qualify. The mask is part of the write data, not part of the command.

Which has a consequence worth stating. The mask is not decided at command time — unlike direction, unlike termination. It travels with the data, so a controller assembling a write burst assembles its mask at the same time, in the same data path, under the same ownership.

2. The Polarity Is Inverted From Intuition

Here is the detail that causes real bugs.

DM_n sampled LOW means the data is masked — that is, not written. Verified: input data is masked when DM_n is sampled low coincident with that input data during a write.

Read that twice, because the intuitive reading of "mask" is backwards. A signal called "mask" being asserted feels like it should mean "apply the mask, block this byte" — and the _n suffix makes it active low, so asserted-low does mean masked. The trap is that engineers think of the wire as a write enable and drive it high to write, which happens to be correct, and then get the inactive case wrong.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   DM_n = 1  (deasserted)  ->  data IS written      (the common case)
   DM_n = 0  (asserted)    ->  data is NOT written   (masked)

3. One Pin, Three Functions — and Sometimes None

Now the fact with the largest architectural consequence.

In DDR4 the ball carries one of three functions, selected by mode register: DM_n (data mask), DBI_n (data bus inversion), or TDQS functionality. They are mutually exclusive — one at a time, never two.

And Chapter 4.5 §3 established why DBI exists: POD12 terminates to the supply so current flows only in one state, which makes the number of lines in that state an energy question. Inverting a byte when inversion reduces the count saves power, and a flag says whether it was inverted.

So a DDR4 x8 or x16 device forces a choice between masking and an energy optimisation. You cannot have both.

In DDR4 a single ball carries the data mask, the data bus inversion flag, or termination data strobe functionality, selected by a mode register so only one can be enabled at a time. On x4 devices the ball does not exist, so neither masking nor bus inversion is available. Where masking is unavailable, a partial write must be performed as a read, a modify and a write, which is a different and considerably more expensive operation.One ballDM / DBI / TDQSMode register picksone at a timeNever twomask or inversionx4 deviceno such ballNo masking at allnot a settingRead-modify-writethe only optionselectsmeansforcesor absent12
Figure 1 — one pin, three mutually exclusive functions, and on x4 devices no pin at all.

On a x4 device there is no ball, so masking is not a setting that is off — it is a capability that does not exist.

And that changes a partial write into a different operation entirely. Without masking, writing part of a burst requires:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   1. READ the full burst from memory
   2. MODIFY the bytes the requester wanted to change
   3. WRITE the full burst back

Read-modify-write, and the cost is not one extra operation. It is a read, a full bus turnaround — with everything Chapter 6.9 §1 said about turnaround being scheduled rather than reactive — and then a write. On top of that the sequence must be atomic with respect to anything else touching those bytes, or a concurrent access can be lost in the window between the read and the write.

So "does this device support masking" is a question with system-architecture consequences, not a datasheet detail. Chapter 5.4 §1 established that a 64-bit rank can be built from 16 x4 devices or 8 x8 devices; this is one of the trade-offs hiding in that choice.

4. RTL — Applying the Mask

Engineering problem

Apply a per-beat, per-byte mask to write data with the correct polarity, and make the unavailable-masking case explicit rather than silently ignoring a mask a device cannot honour.

Classification

SYNTHESIZABLE RTL. Byte-lane commit logic with capability checking — genuinely present in a controller's write data path.

What it represents: the masking decision per beat per byte, the active-low polarity, the DDR4 pin-function exclusivity, and the unavailable case.

What it does not represent: the strobe that samples the mask, the bus ownership it travels under, the read-modify-write sequence §3 describes — that is a controller-level operation and Module 17's — and any electrical property. It also does not implement DBI; it models only that selecting DBI makes masking unavailable, which is the constraint that matters here.

Interface contract

beat_valid with wdata and dm_n presents a beat. commit_bytes says which byte lanes commit. mask_unavailable reports a mask requested where the pin function does not provide one.

State

Saturating telemetry only. The masking decision is combinational, which is correct: it is a property of the beat, sampled with the beat.

Combinational behaviour

The commit decision and the capability check.

Sequential behaviour

Counters.

How to simulate

vlog write_mask_apply.sv tb_write_mask_apply.sv then vsim -c tb_write_mask_apply -do "run -all".

Expected result: bytes whose mask line is high commit and bytes whose mask line is low do not; with PIN_FUNC set to anything but the mask function, any low mask line is reported as unavailable and all bytes commit.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// WRITE MASK APPLY.  Classification: SYNTHESIZABLE RTL.
//
// Per-beat, per-byte write masking.
//
// POLARITY IS INVERTED FROM INTUITION AND CAUSES REAL BUGS:
//   dm_n = 1 (deasserted) -> data IS written    (the common case)
//   dm_n = 0 (asserted)   -> data is NOT written (masked)
//
// The practical consequence is about the DEFAULT, not the polarity: lines
// driven or floating HIGH write everything; lines driven LOW write
// nothing. A polarity inversion therefore produces ALL-OR-NOTHING
// behaviour, never scattered corruption -- which is the diagnostic.
//
// DDR4 PIN SHARING: the ball carries DM_n, DBI_n or TDQS functionality,
// selected by mode register, MUTUALLY EXCLUSIVE. And x4 devices have no
// such ball at all, so masking is not a setting that is off -- it is a
// capability that does not exist. PIN_FUNC models this.
//
// WHAT THIS DOES NOT REPRESENT: the strobe that samples the mask (Chapter
// 6.10), the bus ownership it travels under (Chapter 6.9), the
// read-modify-write sequence that masking's absence forces (a
// controller-level operation, Module 17), DBI's actual inversion logic,
// and every electrical property.
// ─────────────────────────────────────────────────────────────────────────
module write_mask_apply #(
  parameter int BYTES     = 2,
  parameter int MAX_BEATS = 8,
  // DDR4 pin function. 0 = DM (masking available), 1 = DBI, 2 = TDQS,
  // 3 = pin absent (x4). Anything but 0 means NO MASKING.
  parameter int PIN_FUNC  = 0,
  parameter int ACC_W     = 16,
  parameter int BEAT_W    = (MAX_BEATS <= 1) ? 1 : $clog2(MAX_BEATS)
) (
  input  logic                clk,
  input  logic                rst_n,

  input  logic                beat_valid,
  input  logic [BEAT_W-1:0]   beat_index,
  input  logic [BYTES*8-1:0]  wdata,
  // ACTIVE LOW, one per byte lane, sampled WITH the beat -- the mask is
  // part of the write data, not part of the command.
  input  logic [BYTES-1:0]    dm_n,

  // Which byte lanes actually commit to memory.
  output logic [BYTES-1:0]    commit_bytes,
  output logic [BYTES*8-1:0]  commit_data,

  // A mask was requested where the pin function does not provide one.
  // Reported rather than silently ignored: a controller that believes it
  // masked a byte and did not has destroyed data the requester intended
  // to preserve.
  output logic                mask_unavailable,

  output logic [ACC_W-1:0]    cnt_masked_bytes,
  output logic [ACC_W-1:0]    cnt_committed_bytes
);

  localparam int PF_DM     = 0;
  localparam int PF_ABSENT = 3;

  // ── COMPILE-TIME legality.
  if (BYTES < 1) begin : g_bytes
    initial $fatal(1, "write_mask_apply: BYTES must be >= 1");
  end
  if (MAX_BEATS < 1) begin : g_beats
    initial $fatal(1, "write_mask_apply: MAX_BEATS must be >= 1");
  end
  if ((PIN_FUNC < 0) || (PIN_FUNC > 3)) begin : g_pf
    initial $fatal(1, "write_mask_apply: PIN_FUNC must be 0..3");
  end
  // A x4 device has four data lines -- a single byte lane cannot be formed
  // from them, and the absent-pin case is exactly the x4 case. Flagged at
  // elaboration so an impossible configuration cannot be built.
  if ((PIN_FUNC == PF_ABSENT) && (BYTES > 1)) begin : g_absent
    initial $fatal(1, "write_mask_apply: PIN_FUNC=absent models a x4 device; BYTES must be 1");
  end

  // ── Masking availability is an ELABORATION-TIME property, so the check
  //    collapses to a constant and the masking logic disappears entirely
  //    on a device that cannot mask. That is the honest synthesis outcome:
  //    hardware that cannot mask should contain no masking logic.
  localparam bit MASK_OK = (PIN_FUNC == PF_DM);

  // ── The commit decision. dm_n HIGH commits; dm_n LOW masks.
  //
  //    Where masking is unavailable, EVERY byte commits regardless of the
  //    mask lines -- because the device has no mechanism to do otherwise.
  //    Modelling this as "all commit" rather than "mask applied anyway" is
  //    the whole point: it makes the silent data destruction visible.
  always_comb begin
    if (MASK_OK) commit_bytes = dm_n;
    else         commit_bytes = {BYTES{1'b1}};
  end

  assign commit_data = wdata;

  // Any mask requested on a device that cannot honour it.
  assign mask_unavailable = beat_valid && !MASK_OK && (dm_n != {BYTES{1'b1}});

  // ── Saturating telemetry. A controller that cannot report its masked
  //    fraction cannot tell a masking problem from a data problem.
  logic [ACC_W-1:0] masked_now, commit_now;
  always_comb begin
    masked_now = '0;
    commit_now = '0;
    for (int b = 0; b < BYTES; b++) begin
      if (beat_valid) begin
        if (commit_bytes[b]) commit_now = commit_now + ACC_W'(1);
        else                 masked_now = masked_now + ACC_W'(1);
      end
    end
  end

  logic [ACC_W:0] m_sum, c_sum;
  always_comb begin
    m_sum = {1'b0, cnt_masked_bytes}    + {1'b0, masked_now};
    c_sum = {1'b0, cnt_committed_bytes} + {1'b0, commit_now};
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      cnt_masked_bytes    <= '0;
      cnt_committed_bytes <= '0;
    end else if (beat_valid) begin
      cnt_masked_bytes    <= m_sum[ACC_W] ? {ACC_W{1'b1}} : m_sum[ACC_W-1:0];
      cnt_committed_bytes <= c_sum[ACC_W] ? {ACC_W{1'b1}} : c_sum[ACC_W-1:0];
    end
  end

endmodule

Cycle-by-cycle example

BYTES = 2, PIN_FUNC = 0 (masking available):

Cyclebeat_indexdm_ncommit_bytesEffect
101111both bytes written
211010byte 0 masked
320101byte 1 masked
430000both masked — beat writes nothing
541111both written

Cycle 4 is worth noticing. A beat with every mask line asserted transfers data across the bus and commits nothing. The bus was occupied, the energy was spent, and memory is unchanged — which is the honest cost of masking: it does not save bus time, only storage.

Now the same stimulus with PIN_FUNC = 3 (x4, pin absent): commit_bytes is 1 on every beat, every byte commits, and mask_unavailable fires on any beat with a mask requested. The write that was supposed to preserve bytes has overwritten them.

Waveform expectation

§5. Watch commit_bytes track dm_n exactly when masking is available.

Synthesis implication

With PIN_FUNC = 0, a handful of gates plus the counters. With masking unavailable, MASK_OK is an elaboration-time constant and the masking logic optimises away entirely — which is the correct outcome: hardware that cannot mask should contain no masking logic. The capability check costs nothing at runtime because it is resolved at build time.

Corner cases

BYTES == 1 is the x8 case. PIN_FUNC == 3 with BYTES > 1 does not elaborate, because the absent-pin case models a x4 device and a x4 device's four data lines cannot form more than one byte lane. All mask lines asserted commits nothing while still consuming bus time. beat_valid low leaves the counters alone but commit_bytes still reflects dm_n, which is harmless and worth knowing — the output is a function of the inputs regardless of validity.

Debugging clues

If a masked write wrote everything, check the polarity first and the capability second — those are the two causes and they are distinguishable by whether mask_unavailable fired. If it wrote nothing, the polarity is inverted in the other direction. If it wrote the wrong subset, the polarity is correct and the mask values are wrong, which is a data-path problem rather than a control one. The all-or-nothing versus wrong-subset distinction is the highest-value observation in this chapter, and it is available from the first failing write.

Limitations

No strobe, no ownership, no electrical behaviour. No read-modify-write — masking's absence forces it and implementing it is Module 17's controller-level problem, including the atomicity §3 flagged. No DBI implementation: the parameter models only that selecting DBI removes masking. And no mode-register mechanism — PIN_FUNC is a parameter here where a real device is configured at runtime, which means a real controller must handle the function being changed and this block cannot.

5. Masking a Burst

write_mask_apply — a masked write burst on two byte lanes

10 cycles
Ten cycles. An eight-beat write burst is presented on two byte lanes with a per-beat mask. On the first beat both mask lines are deasserted so both bytes commit. On later beats individual lanes are masked, and on one beat both lanes are masked so the beat transfers data across the bus and commits nothing to memory. The masked-byte counter accumulates while the committed-byte counter advances only for unmasked lanes.selective maskingselective maskingfull commitfull commitbyte 0 maskedbyte 0 maskedboth masked — no commitboth masked — no commit12 of 16 bytes committed12 of 16 bytes committedCKbeat_validbeat_index--01234567--wdata--A0A1A2A3A4A5A6A7--dm_n[1:0]11111001001111111111commit_bytes11111001001111111111cnt_masked0001244444cnt_commit002344681012t0t1t2t3t4t5t6t7t8t9
Figure 2 — per beat and per byte; one beat commits nothing while still using the bus.

Cycle 4 is the honest cost of masking. Both mask lines are asserted, commit_bytes is 00, and the beat still happened — data crossed the bus, DQS strobed it, energy was spent, and memory is unchanged. Masking saves storage, not bus time.

Over the burst, 12 of 16 byte-beats committed. That ratio is the metric worth having: a controller that cannot report its masked fraction cannot distinguish a masking problem from a data problem, and on a workload doing many small writes the fraction is a direct measure of wasted bandwidth.

Note that commit_bytes exactly tracks dm_n in this trace. That is the correct behaviour with masking available — and on a device where it is unavailable, commit_bytes would be 11 on every beat regardless, with the write silently overwriting the bytes it was asked to preserve.

Representative educational cycles. The mask pattern is chosen to exercise every combination on two lanes.

6. Three Assertions Worth Writing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// VERIFICATION-ONLY, inside write_mask_apply.

// P1 -- POLARITY. Where masking is available, a byte commits exactly when
// its mask line is deasserted. Written as an equality rather than an
// implication so BOTH inversions are caught -- a design that masked when
// it should commit and one that committed when it should mask each fail.
property p_polarity_exact;
  @(posedge clk) disable iff (!rst_n)
    MASK_OK |-> (commit_bytes == dm_n);
endproperty
assert property (p_polarity_exact);

// P2 -- THE CAPABILITY PROPERTY. Where masking is unavailable, EVERY byte
// commits. This is not a convenience -- it is the honest model of a device
// with no mask pin, and asserting it prevents a well-meaning change from
// "helpfully" applying the mask in logic and hiding the fact that the
// hardware cannot.
property p_no_mask_means_all_commit;
  @(posedge clk) disable iff (!rst_n)
    !MASK_OK |-> (commit_bytes == {BYTES{1'b1}});
endproperty
assert property (p_no_mask_means_all_commit);

// P3 -- and the attempt is reported. A controller that believes it masked
// a byte and did not has destroyed data the requester intended to keep,
// and nothing in the hardware says so.
property p_unavailable_reported;
  @(posedge clk) disable iff (!rst_n)
    (beat_valid && !MASK_OK && (dm_n != {BYTES{1'b1}})) |-> mask_unavailable;
endproperty
assert property (p_unavailable_reported);

// P4 -- the counters partition the byte-beats. Every byte of every valid
// beat is either committed or masked, never both and never neither --
// Chapter 5.7's partition discipline, applied to a small instrument.
property p_counters_partition;
  @(posedge clk) disable iff (!rst_n)
    beat_valid |=> ((cnt_masked_bytes - $past(cnt_masked_bytes))
                  + (cnt_committed_bytes - $past(cnt_committed_bytes))
                  == ACC_W'(BYTES));
endproperty
assert property (p_counters_partition);

P1 as an equality rather than an implication is deliberate. commit_bytes |-> dm_n would catch one inversion and not the other. An equality catches both, and for a two-valued control signal an equality is almost always the right form — an implication leaves half the space unchecked.

P2 is the property that protects an uncomfortable truth. It asserts that a device without a mask pin commits everything, which looks like asserting a bug. It is not: it is the honest model, and without the property a future change might apply the mask in controller logic and appear to fix things while leaving the device's behaviour unchanged — hiding a capability gap rather than surfacing it.

What none of them prove. Nothing about the data being correct. Nothing about the strobe that samples the mask, or the ownership it travels under. Nothing about the read-modify-write sequence that masking's absence forces, including its atomicity — that is Module 17's and is where the real risk lives on a x4 system. And nothing about mode-register changes at runtime, which a real controller must handle.

7. Common Misconceptions

"DM asserted means the data is written." Wrong model: the mask line is a write enable. Why it is tempting: it is active low, so asserting it feels like enabling something, and the common case — deasserted, data written — makes the wire behave like a write enable that happens to be inverted. Consequence: all-or-nothing, not partial corruption. Either the write commits nothing — appearing to have been issued with no effect — or it commits everything and overwrites bytes the requester intended to preserve. The second destroys data that was never part of the request. Correct model: DM_n low means masked, not written. High means written. And the practically important framing is the default: lines high or floating write everything; lines low write nothing. Prevention: ask what the idle state does, not which level means what. The idle state is what a broken or absent driver produces.

"Every DDR4 device supports write masking." Wrong model: masking is a standard capability of the generation. Why it is tempting: it is in the specification, it is in most datasheets, and x8 devices are the common case. Consequence: a controller that issues masked writes to x4 devices, which have no mask pin at all — so every masked byte is written anyway, silently. And on x8 and x16 devices, a controller that enables DBI and then issues masked writes, because the two are mutually exclusive on the shared pin. Correct model: the ball carries DM_n, DBI_n or TDQS, selected by mode register, one at a time. x4 devices do not have the ball, so masking and bus inversion are both unavailable. Masking is a configuration-and-width-dependent capability, not a generation guarantee. Prevention: check the device width and the mode-register setting before assuming masking exists.

"If masking is unavailable, a partial write is just slightly less efficient." Wrong model: the fallback is a minor cost. Why it is tempting: it sounds like a small adjustment, and the alternative operation is made of familiar pieces. Consequence: badly under-estimating a partial-write workload's cost, and — more seriously — missing an atomicity requirement. The fallback is read-modify-write: a read, a full bus turnaround, a modify and a write, and the sequence must be atomic with respect to anything else touching those bytes or a concurrent access is lost between the read and the write. Correct model: it is a different operation, not a slower one. Several times the bus occupancy, a turnaround that Chapter 6.9 §1 showed must be scheduled rather than taken reactively, and a correctness obligation that did not previously exist. Prevention: count the bus operations and ask what happens if something else writes those bytes in between.

"Masking saves bandwidth." Wrong model: masked bytes are not transferred. Why it is tempting: masking reduces what is written, so it feels like it reduces what moves. Consequence: expecting a bandwidth improvement from masking, and mis-modelling a small-write workload's cost. The burst is a fixed size — Chapter 4.4 §5's granularity — and every beat crosses the bus whether or not it commits. Correct model: masking saves storage, never bus time. §5's cycle 4 transfers a full beat and commits nothing. What masking avoids is a read-modify-write, and that is where its value lies — not in reduced transfer. Prevention: ask what the bus did during a masked beat. It carried the data.

8. Debugging — A Write Destroyed Data It Should Not Have Touched

Symptom. A partial write commits more than it should. Bytes the requester intended to preserve have been overwritten with whatever the controller happened to place in those lanes. No errors.

This fault destroys data outside the request's scope, which makes it the command-path-equivalent hazard on the data path — and the corrupted bytes have no relationship to the failing request, so the symptom appears far from the cause.

Mechanism 1 — the device has no mask pin. Inspect: the device width. Expected evidence: a x4 device receiving masked writes. Discriminator: read the width before anything else. Verified: x4 DDR4 devices do not have the DM_n/DBI_n ball, so masking is not off — it is absent, and every masked byte commits. This is first because it is one datasheet lookup and it is a complete explanation.

Mechanism 2 — the pin is configured as DBI or TDQS. Inspect: the mode-register setting for the pin's function. Expected evidence: DBI enabled, masking therefore unavailable, on an x8 or x16 device. Discriminator: read the mode register. The functions are mutually exclusive, so enabling the energy optimisation silently removes masking — and a controller written by someone who enabled DBI for power reasons may not know it did.

Mechanism 3 — polarity inverted. Inspect: whether the write committed everything or the wrong subset. Expected evidence: everything committed, with mask lines driven the wrong way. Discriminator: all-or-nothing, or wrong subset? §2's diagnostic. All-or-nothing means polarity or capability; a wrong subset means the polarity is right and the mask values are wrong, which is a different investigation entirely.

Mechanism 4 — the mask is not aligned with the data. Inspect: whether the correct bytes were preserved but on the wrong beats. Expected evidence: the right number of bytes masked, in the wrong positions. Discriminator: is the masked count right and the position wrong? The mask is sampled with the data by DQS, so a misalignment between the mask lanes and the data beats produces exactly this — and it is a data-path timing problem, not a control one.

Mechanism 5 — read-modify-write lost a concurrent update. Inspect: whether a fallback sequence is in use and whether it is atomic. Expected evidence: corruption only under concurrent access to the same bytes, with the overwritten values being stale rather than arbitrary. Discriminator: are the overwritten bytes stale versions of themselves? §3's atomicity requirement — the read captured a value, something else updated it, and the write put the old value back. Stale-not-arbitrary is the signature, and it is a completely different fault from a masking failure.

Discrimination, cheapest first. Read the device width — one lookup, and it is a complete explanation if it is x4. Then read the mode register's pin-function setting. Then ask whether the write committed everything or the wrong subset. Then check whether overwritten values are stale or arbitrary.

The reasoning lesson. Two of the five mechanisms are "the capability you are using does not exist", and both are resolved by reading configuration rather than by observing behaviour. That is unusual and worth generalising: when a control mechanism appears to be ignored, check whether it is present before checking whether it is correct. A shared pin and a width-dependent ball are both ways for a capability to be absent while the controller's code for it is perfectly correct — and no amount of tracing the mask lines reveals that the device has no pin to receive them.

9. Interview Reasoning

"How does a write commit only part of a burst?" Through the data mask — one line per byte lane, sampled with each beat of the burst, so an eight-beat burst on a two-lane device carries sixteen independent masking decisions on two wires. The mask is source-synchronous with the data and sampled by DQS alongside the beats it qualifies, which means it is part of the write data rather than part of the command — unlike direction or termination, it is not decided at command time. The polarity is inverted from intuition: DM_n sampled low means the data is masked, not written, and deasserted means written.

"What is the practically important thing about DM's polarity?" Not which level means what, but what the idle state does. Lines driven or floating high write everything; lines driven low write nothing. So a polarity error produces all-or-nothing behaviour — either a write that appears to have been issued with no effect, or a write that ignores its mask and overwrites bytes the requester intended to preserve. The second is worse because it destroys data outside the request's scope. It also gives you the diagnostic: all-or-nothing means polarity or capability, while the wrong subset being written means the polarity is right and the mask values are wrong.

"Does every DDR4 device support write masking?" No, in two separate ways. The ball is shared — it carries DM_n, DBI_n or TDQS functionality selected by mode register, and only one can be enabled at a time — so enabling data bus inversion for power reasons silently removes masking. And x4 devices do not have the ball at all, so neither masking nor inversion is available; masking is not a setting that is off, it is a capability that does not exist. That makes "does this device support masking" a width-and-configuration question rather than a generation guarantee.

"What happens when masking is unavailable and a requester wants a partial write?" It becomes read-modify-write: read the full burst, modify the bytes that should change, write the full burst back. That is a different operation rather than a slower one — several times the bus occupancy, plus a full bus turnaround which has to be scheduled rather than taken reactively, plus a correctness obligation that did not previously exist. The sequence must be atomic with respect to anything else touching those bytes, because a concurrent update in the window between the read and the write is lost. That is why device width has system-architecture consequences: a 64-bit rank can be built from sixteen x4 devices or eight x8 devices, and masking availability is one of the trade-offs hiding in that choice.

"A partial write overwrote bytes it should have preserved. Where do you start?" By reading the device width, because x4 devices have no mask ball and that is a complete explanation costing one datasheet lookup. Then the mode-register setting for the shared pin, because the functions are mutually exclusive and enabling DBI removes masking silently. Only then behaviour: did the write commit everything, or the wrong subset? All-or-nothing points at polarity or capability, while the wrong subset means the polarity is fine and the mask values are wrong. And if the overwritten bytes turn out to be stale versions of themselves rather than arbitrary values, that is a read-modify-write sequence that lost a concurrent update — an atomicity failure rather than a masking one. The general lesson is that when a control mechanism appears to be ignored, check whether it is present before checking whether it is correct.

10. Engineering Exercise

Verified generation and width facts; educational burst sizes.

1. An eight-beat burst on a x16 device. How many independent masking decisions, on how many wires? A x16 device has two byte lanes, so 8 × 2 = 16 decisions, carried on two wires sampled eight times each. The mask is dense control information on very few wires, which works because it is source-synchronous with the data.

2. A controller drives all mask lines low for a write. What happens? Nothing commits. The burst is issued, data crosses the bus, DQS strobes it, and memory is unchanged. The write appears to have been issued and to have had no effect — one of the two all-or-nothing polarity failures.

3. The same controller is attached to a x4 device and drives a selective mask. What happens? Every byte commits. The x4 device has no mask ball, so there is nothing to receive the mask. Bytes the requester intended to preserve are overwritten, silently. The controller's code is correct and the capability is absent.

4. An x8 system enables DBI for power reasons and later needs masked writes. What is the conflict? The ball carries DM_n or DBI_n, not both — they are mutually exclusive by mode register. Enabling the energy optimisation removed masking, and the team that enabled it may not have known masking depended on the same pin. The choice is genuine and must be made explicitly.

5. Compute the bus cost of a one-byte write on a device without masking, relative to one with it. With masking: one write burst. Without: a read burst, a bus turnaround, then a write burst — at least three times the bus occupancy, plus the turnaround's scheduling constraint, plus an atomicity requirement. And masking would not have reduced the burst either way — it saves the read, not the transfer.

6. A masked write preserved the right number of bytes in the wrong positions. Which mechanism, and which layer? Mask-to-data misalignment — the count is right so the mask values are right, and only their alignment with the beats is wrong. The mask is sampled with the data by DQS, so this is a data-path timing problem rather than a control problem, and it belongs with Chapter 6.10's window discipline rather than with anything in this chapter.

11. Summary

The data mask lets a write commit only part of a burst, at two granularities simultaneously: per byte — one line per byte lane, the byte being the finest granularity — and per beat, the line being sampled with each beat. An eight-beat burst on two lanes carries sixteen decisions on two wires.

It is part of the write data, not part of the command. Sampled by DQS alongside the beats it qualifies, travelling under the same ownership — unlike direction or termination, it is not decided at command time.

The polarity is inverted from intuition: DM_n low means masked, not written. And the practically important framing is the default — lines high or floating write everything, lines low write nothing. So a polarity error is always all-or-nothing, never scattered corruption, which is the diagnostic.

In DDR4 the ball is shared three waysDM_n, DBI_n or TDQS, selected by mode register, mutually exclusive. Enabling the energy optimisation silently removes masking. And x4 devices have no such ball at all, so masking is not a setting that is off but a capability that does not exist. DDR5 retains DM_n.

Where masking is unavailable, a partial write becomes read-modify-write — a read, a bus turnaround, a modify and a write. That is a different operation, not a slower one: several times the bus occupancy, a turnaround that must be scheduled rather than taken reactively, and an atomicity obligation that did not previously exist. Device width therefore has system-architecture consequences.

Masking saves storage, never bus time. A beat with every lane masked still crosses the bus and spends the energy. What masking avoids is the read-modify-write, and that is where its value is.

And when a control mechanism appears to be ignored, check whether it is present before checking whether it is correct. Two of the five mechanisms in §8 are "the capability does not exist", both resolved by reading configuration rather than observing behaviour — and no amount of tracing the mask lines reveals that the device has no pin to receive them.

12. What Comes Next

Chapter 6.12 closes the module with the signals that did not fit any earlier family.

PAR and ALERT# give the command path integrity protection and a way to report it — and ALERT# is multi-function, reporting two different error classes on one wire, which the controller must distinguish. ACT_n finally gets treated as a signal in its own right rather than as Chapter 6.6's selector. And the trained reference voltages are where Chapter 4.4's measure-rather-design pattern reaches the receiver.

It is also where the module's thesis gets its final test. Twelve chapters in, the question "why does this signal still have a pin of its own" has a consistent answer — and the sideband signals are the ones where that answer is least obvious and most instructive.

Return to DQ for the data path the mask qualifies, DQS for the strobe that samples it, or DDR4 for why data bus inversion exists and competes for the same pin. Module 17 owns read-modify-write. The full path is on the DDR tutorials index.

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.