DDR · Module 25
Dual Sub-Channels
Fourteen command bits with no RAS, CAS or WE at all, commands over one or two cycles, and CS_n inside the command code — plus a mode-register space duplicated per sub-channel.
Three live chapters refuse to model this and say so. Chapter 7.1 §4's command encoder will not elaborate for DDR5 — $fatal(1, "ddr_cmd_encode: GEN must be 3 or 4") — with the comment that “DDR5's CA encoding is not modelled.” Chapter 6.4 §7 adds that in DDR5 “the arrangement changed again, with a differently encoded command/address interface in which RAS# does not appear.” Chapter 6.12 warns that “nothing in this chapter should be carried” across.
Chapter 4.6 §2 then argued at length why a sub-channel needs its own command path, and its scope callout states the boundary precisely: it “deliberately stops short of DDR5's architecture in detail — the sub-channel command encoding, the mode-register space, the bank and bank-group counts per sub-channel.”
Chapter 25.1 took the bank counts. This chapter takes the command encoding and the mode-register space, which is the remaining debt.
1. Fourteen Bits
DEVICE-VERIFIED, from the Micron 16Gb DDR5 die addendum's pin assignments and functional block diagrams: the command and address bus is CA[13:0] — fourteen bits. The block diagrams show it entering the control logic as 14 (CA[13:0]), with a 3 (CA[13:11]) tap into the mode-register path.
The pinout confirms the individual signals CA0 through CA13, alongside CS_n, CK_t/CK_c, and three control pins worth naming now: CA_ODT, CAI and MIR.
Put beside the generations this curriculum has already built, the shape of the change is clear:
| Interface | Command/address signals | Named strobes |
|---|---|---|
| DDR3 / DDR4 | address bus plus RAS_n, CAS_n, WE_n | yes — 6.4 owns them |
| DDR5 | CA[13:0] only | none |
There is no RAS_n, no CAS_n and no WE_n. Chapter 6.4 §7 said the arrangement changed “in which RAS# does not appear” and this is what that means: the strobes are gone entirely, and their function is carried as an opcode inside the fourteen bits.
That is the reason 7.1 §4's encoder refuses to elaborate. Its parameterisation is GEN 3 or 4, and it builds a command from an address plus three named strobes. There is no setting of that block that produces a DDR5 command, because DDR5 has no signal for it to drive.
2. Commands Take One or Two Cycles
DEVICE-VERIFIED, from the addendum's feature list: “1 cycle/2 cycle command structure.”
So a command occupies either one clock cycle or two, per command. DERIVED, and the arithmetic is short:
| Command length | CA bits carried |
|---|---|
| 1 cycle | 14 |
| 2 cycles | 28 |
Fourteen bits is not enough for a command that must carry an opcode plus a bank group, a bank and a row address — 25.1 §1 verified that an x8 device needs three bank-group bits, two bank bits and sixteen row bits, which is twenty-one address bits before any opcode. So an activate cannot be a one-cycle command and a command that needs little cannot justify two. The two lengths exist for the same reason a variable-length encoding usually does: the commands are not all the same size.
3. The Command-Rate Ceiling, and Why Two Buses Double It
A variable-length command encoding has a consequence the bits-per-command table hides: a long command occupies the bus for longer, so the rate at which commands can be issued depends on the mix.
DERIVED, recomputed. With a fraction f of commands taking two cycles, the average is 1 + f cycles per command:
| Two-cycle fraction | Cycles per command | Commands per crossing |
|---|---|---|
| 0% | 1.00 | 1.000 |
| 25% | 1.25 | 0.800 |
| 50% | 1.50 | 0.667 |
| 75% | 1.75 | 0.571 |
| 100% | 2.00 | 0.500 |
A stream of nothing but two-cycle commands issues at half the crossing rate. §2 established that an activate must be a two-cycle command, so a workload with poor row locality — many activates per burst — sits toward the bottom of that table and a streaming workload sits at the top.
This is the same structural point 24.3 §2 made for a different standard: the command bus is a schedulable resource, and a scheduler can saturate it. Chapter 23.4 §5 owns the general principle that a scheduler's job is keeping a resource busy with useful work; here the resource is the CA bus itself.
Now the part that makes 4.6 §2's argument numeric. That chapter calls the per-sub-channel command bus “the load-bearing one” of the duplicated resources, and explains that a shared command bus would mean “one stream serving two data paths.” The quantity that says so is command bandwidth per data bit.
DERIVED, at three command mixes:
| Two-cycle fraction | One shared bus over 64 data bits | Two buses, each over 32 | Ratio |
|---|---|---|---|
| 0% | 0.01562 | 0.03125 | 2× |
| 50% | 0.01042 | 0.02083 | 2× |
| 100% | 0.00781 | 0.01562 | 2× |
Exactly two, at every mix. The ratio is independent of the command mix because both arrangements slow down identically as commands lengthen — so the doubling is a property of the split rather than of the workload.
4. CS_n Is Part of the Command Code
This is the detail that makes DDR5's interface genuinely different in kind, and the datasheet states it outright.
DEVICE-VERIFIED, from the addendum's pin description for CS_n: all commands are masked when CS_n is registered HIGH; CS_n provides external rank selection on multi-rank systems; and — the load-bearing sentence — CS_n is considered part of the command code, and is used to enter and exit power-down and self-refresh.
Compare that with what Chapter 6.3 established for the earlier generations, where chip select qualifies a command that is fully encoded on other signals. Here it is not a qualifier sitting beside the command; it is one of the command's own bits.
Three consequences, and the third is a real controller hazard.
A command's identity depends on a signal that also selects a rank. So the same CA[13:0] pattern means different things depending on CS_n, and a controller cannot form the command and then decide whether to assert it.
Power-down and self-refresh entry and exit are encoded through it, not through a separate signal. Chapter 6.2 owns CKE and states that DDR5's signalling differs, deferring here; this is the difference — the state entry that CKE performed is folded into the command code.
And for a two-cycle command, CS_n's behaviour across the two cycles is part of the framing. A framer that treats CS_n as a per-cycle enable rather than as part of one command spanning two cycles will mis-frame every long command. §16 is about exactly that failure.
5. One Sampling Point Per Clock
DEVICE-VERIFIED, from the addendum's clock pin description: all command/address and control input signals are sampled on the crossing of the positive edge of CK_t and the negative edge of CK_c.
That describes one sampling instant per clock period, defined by a differential crossing rather than by a single-ended edge.
So the command interface is single-data-rate with respect to CK, and a two-cycle command is sampled at two successive crossings. The data bus is a separate matter entirely and is not this chapter's subject.
Two observations worth making because they are easy to get backwards.
A differential crossing is not the same as “both edges”. CK_t rising and CK_c falling are the same instant — that is what a differential pair means. One crossing per period, not two.
And the mode registers are reached through the same fourteen bits. The block diagram's 3 (CA[13:11]) tap and the MRA[7:0] path show mode-register addressing sharing the CA bus rather than having a side channel — which is what makes §11's per-sub-channel mode-register space a command-path concern rather than a configuration detail.
6. 2N Mode Is Listed Separately, and That Matters
The addendum's feature list contains both “1 cycle/2 cycle command structure” and “2N mode” as separate bullets, and the function matrix lists 2N Mode as JEDEC-mandatory at all three widths. DEVICE-VERIFIED that they are two features.
They are therefore not the same thing, and conflating them is easy because both involve the number two and the command bus.
A two-cycle command is an encoding length — how many CA bits the command needs, per §2.
2N mode is something else, listed independently and mandatory regardless of which commands a workload uses.
7. What a Sub-Channel Actually Contains
Now the module side. DEVICE-VERIFIED, from the Micron 262-pin DDR5 SODIMM core specification's product family attributes:
| Attribute | Value |
|---|---|
| DIMM organisation | x64, or x72 with ECC |
| Sub-channels | two 32-bit (non-ECC), two 36-bit (ECC) |
| Pin count | 262 |
| Device densities supported | 16, 24, 32, 64 Gb |
| Device widths | x8, x16 |
| Capacity | 8 GB – 128 GB |
| Package | 78/82-ball FBGA (x8), 102-ball FBGA (x16) |
DERIVED, and it checks:
non-ECC : 2 x 32 bits = 64 -> matches the stated x64
ECC : 2 x 36 bits = 72 -> matches the stated x72So a DDR5 module's data width is two sub-channels, and the module width is their sum. Chapter 4.6 §1 owns why halving the width is not a downgrade and 5.5 §4 owns the framing; what this adds is that the split is 32 bits, not some other division, and that the ECC variant widens each sub-channel rather than adding a third.
8. Four Check Bits Per Thirty-Two
The ECC arithmetic is worth doing explicitly because the ratio is the interesting part.
DERIVED from §7's verified widths:
36-bit sub-channel - 32-bit sub-channel = 4 check bits
4 check bits per 32 data bits = 1 per 8 data bitsFour check bits for thirty-two data bits, which is one per byte. Per sub-channel, independently — the two sub-channels do not share check bits any more than they share a command bus.
Two things follow, and the second is a boundary this chapter must not cross.
ECC is a per-sub-channel property. A correction computed over one sub-channel's 36 bits says nothing about the other's. That is consistent with 4.6 §2's duplication argument and it is worth stating because a module-level “72-bit ECC” framing would imply a single code across the whole width, which the organisation does not support.
And this is system-level ECC, not the device's on-die ECC. Chapter 4.6 §3 is emphatic that treating one as a substitute for the other “is a category error: one protects the array's manufacturability, the other protects the system's data.” The four check bits here are the host's, carried on the module's own data pins. On-die ECC is internal to the device and invisible on these pins, and 25.4 owns it.
9. The Granularity Cancellation Is Exactly Exact
Chapter 4.6 §1 is titled “Why Halving the Width Is Not a Downgrade” and argues that DDR5's granularity cancellation is exact — the burst length doubled as the width halved, so the minimum transfer did not change. It states the claim as reasoning. Two device-verified numbers now let it be checked as arithmetic.
DEVICE-VERIFIED: the die addendum's feature list states a 16n-bit prefetch architecture, and §7 verified the sub-channel is 32 bits.
DERIVED, recomputed:
DDR5 : BL16 x 32-bit sub-channel = 512 bits = 64 B
DDR4 : BL8 x 64-bit channel = 512 bits = 64 BIdentical, to the bit. Not approximately, not close enough — the same 512 bits. So a DDR5 sub-channel's minimum transfer is exactly a DDR4 channel's, which is what makes the halved width a reorganisation rather than a reduction.
Two things worth adding that 4.6 could not.
The ECC case moves more than 64 bytes. With §8's four check bits per thirty-two, a burst on a 36-bit sub-channel carries 16 × 36 = 576 bits — 72 bytes moved for 64 bytes of payload, the extra 8 bytes being check bits. DERIVED. The payload granule is still exactly 64 B; the traffic is 12.5% larger, and that is the cost of host-visible ECC stated in the only currency that matters at this level.
And the granule is per sub-channel, so a module's two sub-channels give two independent 64-byte granules. Chapter 24.3 §9 worked through the equivalent trade for a different standard and found that two narrow channels are never worse and sometimes much better. The same structure applies here — and unlike that case, the granule did not even shrink, because 4.6's cancellation is exact.
10. How Many Devices a Sub-Channel Has
A small piece of arithmetic with a consequence 25.1 §6 referred forward to.
DERIVED from §7's verified sub-channel width and device widths:
| Device width | Devices per 32-bit sub-channel |
|---|---|
| x8 | 4 |
| x16 | 2 |
So an x16-based module puts half as many devices on each sub-channel. That is the reason to choose x16, and 25.1 §7 established what it costs: half the banks and twice the page, with the same 32,768 bytes openable either way.
And the choice is visible at the command bus. Every device on a sub-channel receives that sub-channel's CA[13:0], so four devices means four loads on each of fourteen signals and two means two. Chapter 5.6 owns what electrical loading forces — a buffering layer, and what the buffer costs — and the SODIMM specification's own fly-by topology section is the arrangement that manages it.
11. The Mode-Register Space Is Duplicated Too
Chapter 4.6's boundary sentence names the mode-register space as this chapter's, and it is the item most likely to be overlooked because it is not a signal.
§5 established that mode registers are reached through the same CA[13:0] bus, via the CA[13:11] tap and the MRA[7:0] address path. DEVICE-VERIFIED: the function matrix lists Mode Register Read (MRR), Mode Register Write (MRW), Multi-Purpose Command (MPC) and Per-DRAM Addressability as mandatory at all three widths.
Each sub-channel has its own command bus, therefore its own mode-register space. That follows from §7 rather than being a separate fact, and it produces a specific controller hazard:
A mode-register write configures one sub-channel. A controller that performs initialisation, training or a configuration change once and assumes the module is configured has configured half of it. The other sub-channel retains whatever it had — which after reset is its default, not an error.
12. What Actually Has to Be Written Twice
§11's hazard is abstract until you know how long the list is. The addendum's own function matrix answers that, and the answer is what makes the hazard serious rather than theoretical.
DEVICE-VERIFIED, from the die addendum's DDR5 Function Matrix — features marked supported at x4, x8 and x16, restricted to those that are per-device configuration rather than fixed behaviour:
| Configurable item | What it sets |
|---|---|
| CA Vref Training | the command bus's reference level |
| CS Vref Training | the chip-select reference level |
| DQ Vref Training | the data reference level |
| CS Training Mode (CSTM) | chip-select timing calibration |
| CA Training Mode (CATM) | command-bus timing calibration |
| DFE | decision feedback equalisation — 25.4 owns it |
| CA/CS/CK ODT | termination on the command path |
| Non-target ODT, DQ/DQS park, dynamic WR ODT | termination behaviour by context |
| 2N Mode | listed mandatory; §6 declines to model it |
| Programmable Preamble / Postamble | strobe framing |
| BC8 / OTF | burst chop, on the fly |
| Per-DRAM Addressability | which device a command reaches |
Twelve categories, and at least five of them are training results. Every one is reached through a mode-register write on a sub-channel's own command bus, so every one has to happen twice.
Three observations, and the third is the reason this section exists rather than a sentence in §11.
Most of the list is calibration, not preference. CA Vref, CS Vref, DQ Vref, CSTM and CATM are measured values — Module 21 spends five chapters on how such values are found. A measured value written to one sub-channel leaves the other running on a default that was never measured against its routing, its receivers and its loading.
And the two sub-channels genuinely have different routing. Chapter 4.6 §2 says each sub-channel's path “is measured and calibrated separately, because each has its own routing and its own receivers.” So the defaults are not merely unmeasured — they are unmeasured for a path that differs from the one that was measured. Copying sub-channel A's trained values to B is not a fix either; it is a different bug with the same signature.
Which is why §17's guard tracks items rather than a single done flag. A boolean “initialised” cannot distinguish twelve items written on one side from twelve written on both, and the failure §11's callout describes is precisely that distinction. Tracking the item set per sub-channel is the minimum structure that can detect it.
13. The Sub-Channel as a Structure
Two things the structure shows that the sections do not.
CS_n enters at the same level as CA[13:0], not below it. In a DDR4 diagram chip select would sit beside the command as a qualifier; here it is one of four equal inputs to the command code, which is §4's verified statement drawn rather than asserted. A framer that puts CS_n anywhere else has the wrong architecture.
And the mode-register space is the only node fed by both sub-channels. That is §11's hazard in one picture: the configuration path is reached through each sub-channel's own bus, so there is no single place to write it. The node is drawn once because it is one kind of resource, and it is fed twice because there are two of them — which is exactly the confusion that produces a half-configured module.
14. The CA Framer
// ---------------------------------------------------------------------
// ddr5_ca_framer -- frames a DDR5 command from one or two successive
// CA[13:0] samples, with CS_n treated as part of the command code.
//
// CLASSIFICATION: educational, synthesisable.
//
// THIS IS THE DEBT 7.1 §4 LEFT HERE. That chapter's ddr_cmd_encode
// refuses to elaborate for DDR5 ("GEN must be 3 or 4") because it
// drives RAS_n/CAS_n/WE_n, and §1 verified DDR5 has none of them.
//
// VERIFIED STRUCTURE, ILLUSTRATIVE ENCODING. DEVICE-VERIFIED: the bus
// is CA[13:0]; commands are "1 cycle/2 cycle"; CS_n "is considered
// part of the command code"; sampling is on the CK_t/CK_c crossing.
// The specific opcode-to-length mapping below is ILLUSTRATIVE and is
// NOT reproduced from any datasheet truth table.
//
// WHAT IT DOES NOT MODEL:
// - the device, the electrical interface, or any timing parameter
// - 2N MODE. §6's callout: the addendum names it without defining
// it and the core datasheet did not retrieve, so modelling it
// would be invention. Command length is an input instead.
// - the real DDR5 command set (Module 7 owns DDR3/DDR4's; this is
// not that, and nothing carries across)
//
// WHY CS_n IS NOT AN ENABLE: §4. It is one of the command's own bits.
// A framer that gates on it per cycle mis-frames every two-cycle
// command, which is §16's hazard.
// ---------------------------------------------------------------------
module ddr5_ca_framer #(
parameter int CA_W = 14, // DEVICE-VERIFIED width
parameter int MAX_CY = 2, // DEVICE-VERIFIED maximum length
// The command code is the CA bits of every cycle PLUS the CS_n bit
// of the first cycle, because CS_n is part of the code (§4).
parameter int CODE_W = CA_W * MAX_CY + 1,
parameter int CY_W = $clog2(MAX_CY + 1), // a COUNT, not an index
parameter int CNT_W = 24
) (
input logic clk,
input logic rst_n,
// ── The interface. One sample per CK crossing (§5); there is no
// second sampling point in this block, deliberately.
input logic ck_crossing,
input logic cs_n,
input logic [CA_W-1:0] ca,
// ── Assembled command.
output logic cmd_valid,
output logic [CODE_W-1:0] cmd_code,
output logic [CY_W-1:0] cmd_length,
// CS_n as captured at the command's FIRST cycle, republished
// because it is part of the code and a consumer must not re-sample
// it from the pin (which may have moved on).
output logic cmd_cs_n_at_start,
// ── Bus occupancy. A two-cycle command holds the bus.
output logic bus_busy,
output logic [CNT_W-1:0] cnt_cmds_1cy,
output logic [CNT_W-1:0] cnt_cmds_2cy,
output logic [CNT_W-1:0] cy_bus_occupied,
// ── Malformed framing, reported by kind.
output logic err_cs_deasserted_mid,
output logic err_new_cmd_mid,
output logic err_unknown_length,
output logic any_malformed
);
// ILLUSTRATIVE length mapping from the top two CA bits. NOT a
// datasheet truth table (§14's header).
localparam logic [1:0] L_ONE = 2'b00;
localparam logic [1:0] L_TWO = 2'b01;
initial begin
if (CA_W != 14)
$fatal(1, "ddr5_ca_framer: CA_W must be 14 (device-verified), got %0d", CA_W);
if (MAX_CY != 2)
$fatal(1, "ddr5_ca_framer: MAX_CY must be 2 (device-verified), got %0d", MAX_CY);
if (CODE_W != CA_W * MAX_CY + 1)
$fatal(1, "ddr5_ca_framer: CODE_W must hold both cycles plus the CS_n bit");
if (CY_W < 2)
$fatal(1, "ddr5_ca_framer: CY_W too narrow to count to MAX_CY");
end
logic [CODE_W-1:0] acc, out_code;
logic active, v, cs_start, out_cs;
logic [CY_W-1:0] out_len;
logic [CNT_W-1:0] n1, n2, occ;
logic e_cs, e_new, e_len;
// A command STARTS when CS_n is low at a crossing. Declared above
// the continuous assigns that read it.
logic start;
assign start = ck_crossing && !cs_n;
logic two_cycle, len_known;
always_comb begin
unique case (ca[CA_W-1 -: 2])
L_ONE : begin two_cycle = 1'b0; len_known = 1'b1; end
L_TWO : begin two_cycle = 1'b1; len_known = 1'b1; end
default : begin two_cycle = 1'b0; len_known = 1'b0; end
endcase
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
acc <= '0; active <= 1'b0; v <= 1'b0;
out_code <= '0; out_len <= '0; out_cs <= 1'b0; cs_start <= 1'b0;
n1 <= '0; n2 <= '0; occ <= '0;
e_cs <= 1'b0; e_new <= 1'b0; e_len <= 1'b0;
end else begin
v <= 1'b0;
e_cs <= 1'b0; e_new <= 1'b0; e_len <= 1'b0;
if (ck_crossing) begin
if (active) begin
if (occ != {CNT_W{1'b1}}) occ <= occ + CNT_W'(1);
// ── §4: CS_n belongs to the command, so the SECOND cycle of
// a two-cycle command is part of a command already
// started. A framer gating on CS_n here would treat the
// second cycle as a new command or as no command at all.
if (!cs_n) begin
// CS_n low again mid-command reads as a new command
// starting before the current one finished.
e_new <= 1'b1;
active <= 1'b0;
acc <= '0;
end else begin
// The final sample is folded in COMBINATIONALLY, not read
// back from acc -- acc's write lands on this same edge, so
// reading it would publish the PRE-fold value. This is the
// stale-register hazard 24.3 §12 and Module 21 §13 name.
v <= 1'b1;
out_code <= acc | (CODE_W'(ca) << CA_W);
out_len <= CY_W'(2);
out_cs <= cs_start;
active <= 1'b0;
acc <= '0;
if (n2 != {CNT_W{1'b1}}) n2 <= n2 + CNT_W'(1);
end
end else if (start) begin
if (occ != {CNT_W{1'b1}}) occ <= occ + CNT_W'(1);
cs_start <= cs_n; // the CS_n bit of the code
if (!len_known) begin
e_len <= 1'b1;
end else if (!two_cycle) begin
v <= 1'b1;
out_code <= {1'b0, {(CA_W){1'b0}}, ca} | CODE_W'(cs_n) << (CA_W*2);
out_len <= CY_W'(1);
out_cs <= cs_n;
if (n1 != {CNT_W{1'b1}}) n1 <= n1 + CNT_W'(1);
end else begin
active <= 1'b1;
acc <= CODE_W'(ca) | (CODE_W'(cs_n) << (CA_W*2));
end
end
end
end
end
assign cmd_valid = v;
assign cmd_code = out_code;
assign cmd_length = out_len;
assign cmd_cs_n_at_start = out_cs;
assign bus_busy = active;
assign cnt_cmds_1cy = n1;
assign cnt_cmds_2cy = n2;
assign cy_bus_occupied = occ;
assign err_cs_deasserted_mid = e_cs;
assign err_new_cmd_mid = e_new;
assign err_unknown_length = e_len;
assign any_malformed = e_cs | e_new | e_len;
endmoduleFour decisions in that block carry §4's argument rather than implementation taste.
CS_n is captured at the command's first cycle and republished, not re-sampled by the consumer. §4 established that CS_n is part of the command code, which means it is a bit of a value rather than a state of a wire. By the crossing on which a two-cycle command completes, the pin may already be carrying the next command's CS_n. A consumer reading the pin would sometimes get the right answer and sometimes the following command's — the worst kind of intermittence, and cmd_cs_n_at_start exists so that it cannot happen.
A new command asserted mid-command is rejected and the in-flight command is abandoned rather than completed. Both halves of that are deliberate. Completing it would publish a command whose second cycle came from a different command, fabricating something the controller never issued; silently continuing would hide a scheduler that double-issued. Abandoning and reporting does neither, and err_new_cmd_mid names which of the two mistakes was made.
There is no 2N mode in the block at all. §6's callout establishes that the addendum names the feature without defining it and the core datasheet did not retrieve. A block with a cfg_2n_mode input would look authoritative and encode a guess, so command length is an input and the mode is absent — the same choice 24.5 §10 made about a per-device rail order it could not verify.
And the elaboration guards pin CA_W to 14 and MAX_CY to 2 rather than accepting any width. This is not a general multi-cycle framer that happens to be configured for DDR5. §1 and §3 verified both numbers from a named device, and a parameterisation claiming a twelve-bit CA bus or a four-cycle DDR5 command describes no device this chapter retrieved. Refusing to build is how a verified fact gets enforced rather than merely documented — and it is why 24.3's six-bit four-cycle framer and this one are separate blocks rather than one parameterised family.
15. The Framing Hazard CS_n Creates
§4's third consequence deserves its own section, because it is the mistake a DDR4-shaped controller makes and it is invisible on short commands.
The wrong model: CS_n is an enable. A command is present at a crossing when CS_n is low; when it is high, nothing is happening.
Why that works for one-cycle commands and breaks for two. A two-cycle command starts with CS_n low and continues into a second cycle. If CS_n is treated as a per-cycle enable, the second cycle is either discarded as idle or — worse — a CS_n low on the second cycle is read as a new command starting.
CK crossings : 1 2 3 4
CS_n : low high low high
CA : code_a code_a' code_b code_b'
\_____2-cycle____/ \_____2-cycle____/
CORRECT framing : two commands, each 28 CA bits.
AS AN ENABLE : two commands of 14 bits each, at crossings 1
and 3, and the second half of each DISCARDED.The symptom is that every two-cycle command loses its upper fourteen bits while every one-cycle command is perfectly correct. §1 established that a two-cycle command is what carries a row address — so activates would be wrong and reads within an open row would be right, which points a debugging session at the activate logic rather than at the framer.
§14's block therefore folds the final sample in combinationally, for the same reason 24.3 §12 does: acc's write to the second cycle lands on the edge that publishes the command, so reading acc would publish the value from before that write. Two chapters, two standards, the same defect shape — a value read in the cycle a sequential write to it lands.
16. The Sub-Channel Resource Ledger
// ---------------------------------------------------------------------
// subchannel_resource_ledger -- the per-sub-channel split, with the
// module-width and check-bit arithmetic proven at elaboration.
//
// CLASSIFICATION: educational, synthesisable.
//
// COLLISION NOTE: 4.6 §5's subchannel_router owns granularity as an
// elaboration invariant and parallelism as state, and ROUTES
// requests. This block does neither. It owns the RESOURCE SPLIT --
// what each sub-channel contains and how the widths must agree --
// which is the item 4.6's scope callout assigned to Module 25.
//
// WHAT IT DOES NOT MODEL:
// - the module, any device, or the data path
// - routing (4.6 §5), or any access at all
// - on-die ECC. §8: the check bits here are the HOST's, on module
// data pins. On-die ECC is internal and invisible on them, and
// 4.6 §3 calls conflating the two a category error. 25.4 owns it.
// ---------------------------------------------------------------------
module subchannel_resource_ledger #(
parameter int SUB_CHANNELS = 2, // DEVICE-VERIFIED
parameter int SC_DATA_W = 32, // DEVICE-VERIFIED
parameter bit ECC_PRESENT = 1'b1,
parameter int SC_CHECK_W = ECC_PRESENT ? 4 : 0, // 36 - 32 (§8)
parameter int DEVICE_WIDTH = 8, // x8 or x16
parameter int SC_TOTAL_W = SC_DATA_W + SC_CHECK_W,
parameter int MODULE_W = SUB_CHANNELS * SC_TOTAL_W,
parameter int DEVS_PER_SC = SC_DATA_W / DEVICE_WIDTH,
parameter int SC_IDX_W = (SUB_CHANNELS <= 1) ? 1 : $clog2(SUB_CHANNELS),
parameter int CNT_W = 24
) (
input logic clk,
input logic rst_n,
// ── Per-sub-channel activity, for the independence observation.
input logic [SUB_CHANNELS-1:0] sc_busy,
// ── The split, published.
output logic [7:0] n_sub_channels,
output logic [7:0] sc_data_width,
output logic [7:0] sc_check_width,
output logic [15:0] module_width,
output logic [7:0] devices_per_sc,
// Check-bit ratio as a PAIR, never a single figure -- the same
// no-divide discipline 25.1 §10 uses.
output logic [7:0] check_num,
output logic [7:0] check_den,
// ── Concurrency observation. 4.6 §2 warns that a controller
// treating two sub-channels as one wide channel "gets DDR4's
// parallelism out of DDR5 hardware". Only a count shows it.
output logic [CNT_W-1:0] cy_both_busy,
output logic [CNT_W-1:0] cy_one_busy,
output logic [CNT_W-1:0] cy_idle,
output logic concurrency_observed,
output logic any_saturated
);
initial begin
if (SUB_CHANNELS != 2)
$fatal(1, "subchannel_resource_ledger: SUB_CHANNELS must be 2 (device-verified)");
if (SC_DATA_W != 32)
$fatal(1, "subchannel_resource_ledger: SC_DATA_W must be 32 (device-verified)");
if (ECC_PRESENT && (SC_CHECK_W != 4))
// §8: 36 - 32 = 4. A different check width is not this module.
$fatal(1, "subchannel_resource_ledger: ECC sub-channel is 36 bits, so 4 check bits");
if (!ECC_PRESENT && (SC_CHECK_W != 0))
$fatal(1, "subchannel_resource_ledger: non-ECC sub-channel carries no check bits");
// §7's arithmetic: the two sub-channels must sum to x64 or x72.
if (!(MODULE_W inside {64, 72}))
$fatal(1, "subchannel_resource_ledger: module width %0d is neither x64 nor x72",
MODULE_W);
if (!(DEVICE_WIDTH inside {8, 16}))
// DEVICE-VERIFIED: the SODIMM specification supports x8 and x16.
$fatal(1, "subchannel_resource_ledger: DEVICE_WIDTH must be 8 or 16");
if ((SC_DATA_W % DEVICE_WIDTH) != 0)
$fatal(1, "subchannel_resource_ledger: %0d devices do not tile a %0d-bit sub-channel",
DEVICE_WIDTH, SC_DATA_W);
if (DEVS_PER_SC < 1)
$fatal(1, "subchannel_resource_ledger: need at least one device per sub-channel");
end
logic [CNT_W-1:0] c_both, c_one, c_idle;
logic saw, sat;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_both <= '0; c_one <= '0; c_idle <= '0; saw <= 1'b0; sat <= 1'b0;
end else begin
// Exhaustive three-way classification of every cycle, so the
// counters partition time and P8 can assert it.
case ($countones(sc_busy))
2'd2 : begin
if (c_both != {CNT_W{1'b1}}) c_both <= c_both + CNT_W'(1); else sat <= 1'b1;
saw <= 1'b1;
end
2'd1 : if (c_one != {CNT_W{1'b1}}) c_one <= c_one + CNT_W'(1); else sat <= 1'b1;
default : if (c_idle != {CNT_W{1'b1}}) c_idle <= c_idle + CNT_W'(1); else sat <= 1'b1;
endcase
end
end
assign n_sub_channels = 8'(SUB_CHANNELS);
assign sc_data_width = 8'(SC_DATA_W);
assign sc_check_width = 8'(SC_CHECK_W);
assign module_width = 16'(MODULE_W);
assign devices_per_sc = 8'(DEVS_PER_SC);
assign check_num = 8'(SC_CHECK_W);
assign check_den = 8'(SC_DATA_W);
assign cy_both_busy = c_both;
assign cy_one_busy = c_one;
assign cy_idle = c_idle;
assign concurrency_observed = saw;
assign any_saturated = sat;
endmodulecy_both_busy exists for the reason 4.6 §2's closing sentence gives: a controller that treats two sub-channels as one wide channel “gets DDR4's parallelism out of DDR5 hardware.” That failure produces no error and violates no property — it shows up only as a zero in this counter, which is why the counter is an output rather than a debug aid.
17. The Mode-Register Space Guard
// ---------------------------------------------------------------------
// subchannel_mr_guard -- refuses to call a module configured until
// every configurable item has been written to BOTH sub-channels.
//
// CLASSIFICATION: educational, synthesisable.
//
// WHY IT EXISTS: §11. Each sub-channel has its own command bus and
// therefore its own mode-register space. A controller that writes a
// setting once has configured half the module, and the half-
// configured module WORKS -- it just behaves differently under the
// conditions the setting was for. Nothing reports an error, because
// a device at its default is in a valid state.
//
// WHAT IT DOES NOT MODEL:
// - any real mode register, its encoding or its address map
// - what any setting DOES. It tracks completeness, not meaning.
// - training itself (Module 21), only that a trained result must
// be written to both sub-channels to take effect on both.
// ---------------------------------------------------------------------
module subchannel_mr_guard #(
parameter int SUB_CHANNELS = 2,
parameter int N_ITEMS = 8, // configurable items tracked
parameter int ITEM_W = (N_ITEMS <= 1) ? 1 : $clog2(N_ITEMS),
// A COUNT of items up to N_ITEMS needs clog2(N_ITEMS+1).
parameter int ITEMCNT_W = $clog2(N_ITEMS + 1),
parameter int SC_IDX_W = (SUB_CHANNELS <= 1) ? 1 : $clog2(SUB_CHANNELS)
) (
input logic clk,
input logic rst_n,
// ── A mode-register write to one sub-channel.
input logic mrw_valid,
input logic [SC_IDX_W-1:0] mrw_sc,
input logic [ITEM_W-1:0] mrw_item,
// ── An access that depends on configuration being complete.
input logic acc_valid,
input logic [SC_IDX_W-1:0] acc_sc,
// ── Completeness.
output logic [N_ITEMS-1:0] written_sc0,
output logic [N_ITEMS-1:0] written_sc1,
output logic [ITEMCNT_W-1:0] n_complete,
output logic module_configured,
output logic sc0_configured,
output logic sc1_configured,
// §11's hazard, named: exactly one sub-channel is fully configured.
// The state in which a module works and misbehaves.
output logic half_configured,
output logic err_item_out_of_range,
output logic err_access_before_config,
output logic any_error
);
initial begin
if (SUB_CHANNELS != 2)
$fatal(1, "subchannel_mr_guard: SUB_CHANNELS must be 2 (device-verified)");
if (N_ITEMS < 1)
$fatal(1, "subchannel_mr_guard: N_ITEMS must be positive");
if (ITEMCNT_W <= ITEM_W && N_ITEMS > 1)
// A count up to N_ITEMS needs one more bit than an index into
// it when N_ITEMS is a power of two. Getting this wrong makes
// module_configured UNREACHABLE -- the silent form.
$fatal(1, "subchannel_mr_guard: ITEMCNT_W too narrow to count N_ITEMS");
end
logic [N_ITEMS-1:0] w0, w1;
logic e_item, e_early;
// Declared above the continuous assigns that read them.
logic item_ok;
assign item_ok = ({{(32-ITEM_W){1'b0}}, mrw_item} < 32'(N_ITEMS));
logic [ITEMCNT_W-1:0] n_done;
always_comb begin
n_done = '0;
for (int i = 0; i < N_ITEMS; i++)
if (w0[i] && w1[i]) n_done = n_done + ITEMCNT_W'(1);
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// Reset clears BOTH sub-channels' records. A reset that
// remembered one side would let a controller skip half the
// configuration after a reset and be told it was complete.
w0 <= '0; w1 <= '0; e_item <= 1'b0; e_early <= 1'b0;
end else begin
e_item <= 1'b0; e_early <= 1'b0;
if (mrw_valid) begin
if (!item_ok) e_item <= 1'b1;
else if (mrw_sc == '0) w0[mrw_item] <= 1'b1;
else w1[mrw_item] <= 1'b1;
end
// An access to a sub-channel whose configuration is incomplete
// is reported. It is not blocked -- §11: it works, which is the
// whole problem, and a block that prevented it would model a
// device behaviour that does not exist.
if (acc_valid) begin
if ((acc_sc == '0) && (w0 != {N_ITEMS{1'b1}})) e_early <= 1'b1;
if ((acc_sc != '0) && (w1 != {N_ITEMS{1'b1}})) e_early <= 1'b1;
end
end
end
assign written_sc0 = w0;
assign written_sc1 = w1;
assign n_complete = n_done;
assign sc0_configured = (w0 == {N_ITEMS{1'b1}});
assign sc1_configured = (w1 == {N_ITEMS{1'b1}});
assign module_configured = sc0_configured && sc1_configured;
// Exactly one side complete: the dangerous state of §11's callout.
assign half_configured = sc0_configured ^ sc1_configured;
assign err_item_out_of_range = e_item;
assign err_access_before_config = e_early;
assign any_error = e_item | e_early;
endmodulehalf_configured is an output rather than an internal condition because it is the state §11's callout describes: the module works, returns data, reports nothing, and misbehaves only under the conditions the missing configuration was for. Naming it is the whole contribution — a controller cannot avoid a state it has no word for.
And an incomplete access is reported, not blocked. Blocking it would model a device behaviour that does not exist: a device at its default configuration accepts commands perfectly well. The guard's job is to make the omission visible, not to invent an interlock.
18. What the Assertions Prove
// Bind unit note: P1-P6 reference ddr5_ca_framer's internals, P7-P10
// subchannel_resource_ledger's, P11-P15 subchannel_mr_guard's.
// P1 -- a command is never longer than the device-verified maximum.
property p_length_bounded;
@(posedge clk) disable iff (!rst_n)
cmd_valid |-> (cmd_length inside {CY_W'(1), CY_W'(2)});
endproperty
assert property (p_length_bounded);
// P2 -- FORBIDDEN. A one-cycle command never occupies the bus past
// its own crossing, so it can never be mistaken for the first half of
// a two-cycle command.
property p_one_cycle_does_not_occupy;
@(posedge clk) disable iff (!rst_n)
(cmd_valid && (cmd_length == CY_W'(1))) |-> !bus_busy;
endproperty
assert property (p_one_cycle_does_not_occupy);
// P3 -- §15's hazard. A two-cycle command's published code must carry
// the SECOND cycle's CA bits, which the stale-register bug omits.
property p_second_cycle_included;
@(posedge clk) disable iff (!rst_n)
(cmd_valid && (cmd_length == CY_W'(2)) && ($past(ca, 1) != '0))
|-> (cmd_code[CA_W*2-1 -: CA_W] != '0);
endproperty
assert property (p_second_cycle_included);
// P4 -- §4: CS_n is part of the code, so the published command
// republishes the CS_n captured at its FIRST cycle. A consumer
// re-sampling the pin could read a value from a later command.
property p_cs_is_from_first_cycle;
@(posedge clk) disable iff (!rst_n)
(cmd_valid && (cmd_length == CY_W'(2)))
|-> (cmd_cs_n_at_start == cmd_code[CA_W*2]);
endproperty
assert property (p_cs_is_from_first_cycle);
// P5 -- FORBIDDEN. A malformed framing is never published. Framing a
// command from a broken sequence would fabricate a command the
// controller never issued, which is worse than losing one.
property p_malformed_never_published;
@(posedge clk) disable iff (!rst_n)
any_malformed |-> !cmd_valid;
endproperty
assert property (p_malformed_never_published);
// P6 -- nothing is framed without a clock crossing. §5: there is one
// sampling instant per period and no other.
property p_no_command_without_crossing;
@(posedge clk) disable iff (!rst_n)
!ck_crossing |=> (!cmd_valid && (bus_busy == $past(bus_busy, 1)));
endproperty
assert property (p_no_command_without_crossing);
// P7 -- §7's arithmetic as an invariant: the sub-channels sum to the
// module width, and it is x64 or x72.
property p_module_width_is_the_sum;
@(posedge clk) disable iff (!rst_n)
((module_width == 16'(SUB_CHANNELS) * 16'(SC_DATA_W + SC_CHECK_W)) &&
(module_width inside {16'd64, 16'd72}));
endproperty
assert property (p_module_width_is_the_sum);
// P8 -- the three occupancy classes partition every cycle exactly.
property p_occupancy_partitions_cycles;
@(posedge clk) disable iff (!rst_n)
((cy_both_busy + cy_one_busy + cy_idle) ==
($past(cy_both_busy,1) + $past(cy_one_busy,1) + $past(cy_idle,1) + CNT_W'(1)));
endproperty
assert property (p_occupancy_partitions_cycles);
// P9 -- §8's ratio: the check-bit pair is 4 over 32 when ECC is
// present and 0 over 32 when it is not.
property p_check_ratio_is_verified;
@(posedge clk) disable iff (!rst_n)
((check_den == 8'd32) && (check_num == (ECC_PRESENT ? 8'd4 : 8'd0)));
endproperty
assert property (p_check_ratio_is_verified);
// P10 -- §10's device count: devices tile the sub-channel exactly.
property p_devices_tile_the_subchannel;
@(posedge clk) disable iff (!rst_n)
((devices_per_sc * 8'(DEVICE_WIDTH)) == 8'(SC_DATA_W));
endproperty
assert property (p_devices_tile_the_subchannel);
// P11 -- FORBIDDEN. The module is never reported configured while
// either sub-channel is incomplete. §11's hazard as a property.
property p_never_configured_while_half;
@(posedge clk) disable iff (!rst_n)
module_configured |-> (sc0_configured && sc1_configured);
endproperty
assert property (p_never_configured_while_half);
// P12 -- half_configured means exactly one side is complete, which is
// the state §11's callout names.
property p_half_means_exactly_one;
@(posedge clk) disable iff (!rst_n)
half_configured == (sc0_configured ^ sc1_configured);
endproperty
assert property (p_half_means_exactly_one);
// P13 -- FORBIDDEN. Configured and half-configured are never both
// true. Rules out reporting completeness during the dangerous state.
property p_configured_and_half_exclusive;
@(posedge clk) disable iff (!rst_n) !(module_configured && half_configured);
endproperty
assert property (p_configured_and_half_exclusive);
// P14 -- a written item is never forgotten except by reset. A record
// that could clear would let a controller be told a write took effect
// and later that it did not.
property p_written_items_are_sticky;
@(posedge clk) disable iff (!rst_n)
(($past(written_sc0,1) & written_sc0) == $past(written_sc0,1));
endproperty
assert property (p_written_items_are_sticky);
// P15 -- FORBIDDEN. An out-of-range item index never records anything.
property p_bad_item_records_nothing;
@(posedge clk) disable iff (!rst_n)
err_item_out_of_range |-> ((written_sc0 == $past(written_sc0,1)) &&
(written_sc1 == $past(written_sc1,1)));
endproperty
assert property (p_bad_item_records_nothing);
// ── Cover.
// Both command lengths actually framed.
cover property (@(posedge clk) disable iff (!rst_n)
cmd_valid && (cmd_length == CY_W'(1)));
cover property (@(posedge clk) disable iff (!rst_n)
cmd_valid && (cmd_length == CY_W'(2)));
// Each malformed kind attempted -- a forbidding property is vacuous
// until the testbench tries it.
cover property (@(posedge clk) disable iff (!rst_n) err_new_cmd_mid);
cover property (@(posedge clk) disable iff (!rst_n) err_unknown_length);
// Back-to-back two-cycle commands: the framing §15 says an enable
// model gets wrong, actually exercised.
cover property (@(posedge clk) disable iff (!rst_n)
(cmd_valid && (cmd_length == CY_W'(2)))
##1 bus_busy ##1 (cmd_valid && (cmd_length == CY_W'(2))));
// THE CONCURRENCY COVER. Both sub-channels busy at once -- 4.6 §2's
// warning is invisible without it.
cover property (@(posedge clk) disable iff (!rst_n) sc_busy == 2'b11);
cover property (@(posedge clk) disable iff (!rst_n) sc_busy == 2'b00);
// Both module widths exercised.
cover property (@(posedge clk) disable iff (!rst_n) module_width == 16'd64);
cover property (@(posedge clk) disable iff (!rst_n) module_width == 16'd72);
// THE HALF-CONFIGURED STATE, reached deliberately. §11's hazard is
// vacuous as a property unless a suite enters it.
cover property (@(posedge clk) disable iff (!rst_n) half_configured);
cover property (@(posedge clk) disable iff (!rst_n) module_configured);
cover property (@(posedge clk) disable iff (!rst_n) err_access_before_config);
cover property (@(posedge clk) disable iff (!rst_n) err_item_out_of_range);19. Corner Cases
| Case | Behaviour | Why |
|---|---|---|
| One-cycle command | Framed on its own crossing; bus not busy | P2 — cannot be confused with a long command's first half |
| Two-cycle command | Framed across two crossings; 28 CA bits | §2 |
CS_n treated as a per-cycle enable | Every long command loses its upper 14 bits | §15 — activates wrong, reads right |
Second cycle folded from acc | Same symptom; P3 catches it | §15's stale-register hazard |
CS_n low again on the second cycle | err_new_cmd_mid; nothing published | A new command cannot start mid-command |
| Unknown length encoding | err_unknown_length; nothing framed | Guessing would mis-frame every following command |
| No clock crossing | Nothing framed; occupancy unchanged | P6 — §5's single sampling instant |
| A stream of only two-cycle commands | 0.500 commands per crossing | §3 — half the rate a short-command stream achieves |
| A stream of only one-cycle commands | 1.000 per crossing | §3 — the ceiling, and the only mix that reaches it |
| Sub-channel burst of BL16 x 32 bits | Exactly 64 B, matching DDR4's BL8 x 64 | §9 — the cancellation is exact to the bit |
| The same burst with ECC | 72 B moved for 64 B of payload | §9 — 12.5% more traffic, the cost of host-visible ECC |
| Module width neither 64 nor 72 | $fatal at elaboration | §7's verified arithmetic |
| ECC present with a check width other than 4 | $fatal at elaboration | §8 — 36 − 32 = 4 |
| Device width other than x8 or x16 | $fatal at elaboration | DEVICE-VERIFIED support list |
| Devices that do not tile the sub-channel | $fatal at elaboration | P10 — a partial device is not a configuration |
| Both sub-channels busy | Legal and counted | The architecture's purpose |
| Sub-channels never both busy | No error; cy_both_busy is zero | 4.6 §2 — DDR4's parallelism from DDR5 hardware |
| One sub-channel configured, one not | half_configured; module not configured | P11, P13 — §11's hazard |
| Access to an unconfigured sub-channel | Reported, not blocked | §17 — a defaulted device accepts commands |
| Reset with one side configured | Both records cleared | Remembering one side would let a controller skip half |
ITEMCNT_W too narrow for N_ITEMS | $fatal at elaboration | module_configured would be unreachable — the silent form |
Row thirteen is the one this chapter exists to put in a table. “Both sub-channels work and performance is single-channel” produces no error and violates no property, and the only evidence is a zero in an occupancy counter.
20. DV
// Independent framing reference. Assembles commands from a QUEUE of
// (cs_n, ca) samples rather than from a state machine, so agreement
// with the DUT is evidence and not a restatement. SIMULATION-ONLY.
class ddr5_framing_ref;
typedef struct { bit cs_n; bit [13:0] ca; } samp_t;
samp_t q[$];
int need;
int n_published;
bit first_cs_n;
function void reset(); q.delete(); need = 0; n_published = 0; endfunction
function int length_of(bit [13:0] first);
case (first[13:12])
2'b00 : return 1;
2'b01 : return 2;
default: return -1; // unknown
endcase
endfunction
// Returns the framed width in bits on completion, 0 while
// assembling, -1 on a malformed sequence.
function int sample(bit crossing, bit cs_n, bit [13:0] ca);
samp_t s;
if (!crossing) return 0;
if (q.size() == 0) begin
if (cs_n) return 0; // no command starts
need = length_of(ca);
if (need < 0) return -1;
first_cs_n = cs_n;
s.cs_n = cs_n; s.ca = ca; q.push_back(s);
end else begin
// CS_n LOW mid-command is a new command starting early.
if (!cs_n) begin q.delete(); need = 0; return -1; end
s.cs_n = cs_n; s.ca = ca; q.push_back(s);
end
if (q.size() == need) begin
int w = q.size() * 14;
q.delete(); need = 0; n_published++;
return w;
end
return 0;
endfunction
endclass| Check | What it establishes |
|---|---|
| Every framed command matches the reference bit-for-bit | Two representations agree |
| Publish counts match over a long random stream | No command lost or duplicated |
| Frame both lengths; compare the 14- and 28-bit codes | The two length covers, and P3 on the long case |
Assert CS_n low on the second cycle of a long command | err_new_cmd_mid; nothing published |
| Drive the unknown length encoding | err_unknown_length; the next command frames correctly |
Hold ck_crossing low for many cycles | P6 — nothing framed, occupancy frozen |
Replace the combinational fold with out_code <= acc | P3 fires; one-cycle commands still pass |
Model CS_n as a per-cycle enable in the reference | The reference and DUT diverge on every long command |
| Parameterise x64 and x72; check both module widths | P7 and the two width covers |
| Parameterise x8 and x16; check the device count | P10 — 4 and 2 |
| Drive both sub-channels simultaneously | The 2'b11 concurrency cover |
| Drive sub-channels strictly alternately | cy_both_busy stays zero — §19's row thirteen |
| Write every item to sub-channel 0 only | half_configured; module not configured |
| Then write them to sub-channel 1 | module_configured asserts; half_configured clears |
| Access sub-channel 1 while only 0 is configured | err_access_before_config |
| Reset after configuring one side | Both records clear |
The thirteenth and fourteenth checks together produce the report worth publishing, because the failure is a pass on every functional measure:
THE MODULE THAT WAS CONFIGURED ONCE
Initialisation writes all 8 items to sub-channel 0 and, by an
off-by-one in the sub-channel loop, never to sub-channel 1.
written_sc0 8'b11111111
written_sc1 8'b00000000
n_complete 0
sc0_configured 1
sc1_configured 0
module_configured 0
half_configured 1 <-- the state that has a name
functional test : read and write every address. ALL PASS.
both sub-channels respond.
both return correct data.
no error, no alert, no timeout.
assertions : P11 never configured while half PASS
P12 half means exactly one PASS
P13 configured and half exclusive PASS
all 15 properties PASS
diagnosis : nothing is broken. Sub-channel 1 is running at its
RESET DEFAULTS, which is a valid state for a device to be in.
It answers commands correctly at low rate and at room
temperature, and the settings it is missing are exactly the
ones that matter at rate and at temperature.
what makes it dangerous : the defect is invisible to every test
that does not check configuration completeness, and it
presents later as intermittent data corruption under load --
which is Module 21's training-failure signature, on a module
whose training succeeded.
the fix : half_configured is a REQUIRED coverage goal and a
REQUIRED gate before declaring initialisation complete. A
controller that cannot distinguish "configured" from
"configured on one side" will ship this bug.21. Debugging
| Symptom | Likely cause | How to confirm |
|---|---|---|
| Short commands work, long ones do not | CS_n modelled as an enable — §15 | Every two-cycle command missing its upper 14 bits |
| Activates fail, reads within a row succeed | The same bug — activates are the long commands | cnt_cmds_2cy against observed activate behaviour |
| Every command after a point is wrong | A length mis-framed, shifting all later framing | err_unknown_length; inspect the first bad command |
| Commands appear that were never issued | Malformed framing published — must not happen | P5; if it fires, the abandon path is broken |
| Nothing frames at all | No clock crossing reaching the framer | P6; cy_bus_occupied static |
| Trying to reuse Module 7's encoder for DDR5 | It $fatals by design — §1 | 7.1 §4; DDR5 has no RAS_n/CAS_n/WE_n |
| Module width does not elaborate | Sub-channel or check width wrong — §7, §8 | The $fatal names the computed width |
| Half the expected bandwidth | Only one sub-channel in use | cy_one_busy against cy_both_busy |
| Both sub-channels work, performance is single-channel | Lockstep driving — §19 row thirteen | cy_both_busy is zero |
| Intermittent corruption at rate and temperature | A half-configured module — §11 | half_configured; written_sc0 against written_sc1 |
| Corruption on half the address space only | The unconfigured sub-channel's half | Which sub-channel the failing addresses map to |
| Initialisation reports success, behaviour is wrong | Configured once, not twice | module_configured should be low; if high, the guard is bypassed |
Row ten is the signature to memorise. Intermittent corruption at rate on a module whose training succeeded points at configuration completeness, not at training — and Module 21's debugging chapters will send you looking in the wrong place if you do not know §11's hazard exists.
22. Misconceptions
“DDR5 has a wider address bus plus the usual strobes.” §1. CA[13:0] and nothing else — no RAS_n, no CAS_n, no WE_n. Their function is an opcode inside the fourteen bits.
“Module 7's command encoder can be parameterised for DDR5.” §1. It refuses to elaborate, correctly. There is no setting that produces a command for an interface with no strobe signals.
“All DDR5 commands are two cycles.” §2. One or two, per command. A command needing few bits does not spend a second cycle.
“DDR5's CA bus is LPDDR4's idea scaled up.” §2's callout. Fourteen bits against six, two lengths against three, a different standard. The shared idea is a pattern, not a parameter.
“CS_n qualifies the command.” §4. It is part of the command code, which is the datasheet's own phrasing, and power-down and self-refresh entry are encoded through it.
“Commands are sampled on both clock edges.” §5. On the crossing of CK_t rising and CK_c falling — which is one instant per period, because that is what a differential pair means.
“2N mode is the two-cycle command structure.” §6. The datasheet lists them as separate features and marks 2N mode JEDEC-mandatory independently. This chapter does not model it and says why.
“A variable-length encoding is free.” §3. A stream of two-cycle commands issues at half the rate of a short-command stream, and an activate must be a long command.
“Two command buses is one duplication among three.” §3's callout. It is the one that makes the other two useful — at exactly 2x the command bandwidth per data bit, independent of the command mix.
“Halving the width halved the transfer size.” §9. BL16 x 32 bits is 512 bits; DDR4's BL8 x 64 is 512 bits. Identical to the bit.
“Granularity is unchanged, so nothing changed.” §9's callout. The transfer size cancelled; the transfer duration did not — sixteen beats instead of eight, which is what 25.4's integrity mechanisms must cover.
“Copying sub-channel A's trained values to B is a reasonable shortcut.” §12. Each sub-channel has its own routing and receivers, so A's measured values are wrong for B — a different bug with the same signature.
“A DDR5 module is a 64-bit channel.” §7. It is two 32-bit sub-channels, or two 36-bit with ECC, and their sum is the module width.
“ECC adds 8 bits to a 64-bit module.” §8. It adds 4 bits to each 32-bit sub-channel — one per byte, per sub-channel, with no shared code across the module.
“On-die ECC and the module's check bits are the same protection.” §8. Chapter 4.6 §3 calls that a category error: one protects the array's manufacturability, the other the system's data.
“x16 just means fewer, wider devices.” §10. Two per sub-channel instead of four — and 25.1 §7 established it also means half the banks and twice the page.
“One mode-register write configures the module.” §11. It configures one sub-channel. The other keeps its defaults, which is a valid state and therefore reports nothing.
“A half-configured module fails.” §11's callout. It works — and misbehaves only under the conditions the missing setting was for, which is why the bug reaches production.
23. Interview Reasoning
How does DDR5 encode a command? On a fourteen-bit CA bus with no separate RAS_n, CAS_n or WE_n — the function is an opcode within those bits — over one or two clock cycles, sampled on the CK_t/CK_c crossing.
Why can't a DDR4 command encoder be reused? It drives three named strobes that do not exist on DDR5. There is no parameterisation of a strobe-driving encoder that produces a strobeless command, which is why 7.1's block $fatals rather than offering a DDR5 mode.
What is unusual about CS_n in DDR5? It is part of the command code rather than a qualifier beside it, and power-down and self-refresh entry and exit are encoded through it. So the same CA pattern means different things depending on it.
What goes wrong if you treat CS_n as a per-cycle enable? Every two-cycle command loses its second half. One-cycle commands stay perfectly correct, so activates fail while reads within an open row succeed — which points debugging at the wrong block.
How many bits does a two-cycle command carry? Twenty-eight — fourteen per cycle. A single cycle's fourteen bits cannot hold an opcode plus the twenty-one address bits an x8 activate needs.
What limits how fast commands can be issued? The mix. One-cycle commands issue at one per clock crossing, two-cycle at one per two, so a mix with fraction f long commands averages 1/(1+f). An activate is a long command, so poor row locality pushes you toward 0.5.
Show that DDR5's per-sub-channel command bandwidth beats a shared bus. Per data bit, two buses over 32 bits each give exactly twice what one bus over 64 bits gives — at every command mix, because both slow identically as commands lengthen.
Did halving the channel width halve the minimum transfer? No, and it is exact rather than approximate: sixteen beats of 32 bits is 512 bits, and eight beats of 64 bits is also 512 bits. What doubled is the number of beats, not the bytes.
What is a DDR5 sub-channel? Thirty-two data bits, or thirty-six with ECC, with its own command bus, clock, chip select, mode-register space and trained timing state. A module is two of them, summing to x64 or x72.
How many check bits does DDR5 ECC use, and over what? Four per 32-bit sub-channel — one per byte — computed per sub-channel, with no code spanning the module. That is the host's ECC, separate from the device's internal on-die ECC.
How many devices are on a sub-channel? Four at x8, two at x16. That is the reason to choose x16, and the cost is half the banks and twice the page.
What is the risk of a per-sub-channel mode-register space? Configuring once configures half the module. The other sub-channel runs at its defaults, which is a valid state, so nothing reports an error and the module works — failing only at rate and temperature, where the missing setting mattered.
Your DDR5 controller gets DDR4-level performance from working hardware. Where do you look? At whether both sub-channels are ever busy simultaneously. Driving them in lockstep violates nothing and produces exactly DDR4's parallelism.
24. Exercises
-
An x8 DDR5 activate needs the bank-group, bank and row fields 25.1 §1 verified. Compute the minimum opcode space left in a two-cycle command, and show that a one-cycle command cannot carry it under any encoding.
-
§15 shows the enable-model bug corrupts only long commands. Write the shortest command sequence that distinguishes a correct framer from the buggy one, and say which observable signal reveals it.
-
Remove the combinational fold in §14 and replace it with
out_code <= acc. Which of P1 through P6 fire? Explain why P1 and P2 do not. -
§8 gives four check bits per 32 data bits. Compute the overhead as a fraction and compare it against a code spanning the full 72-bit module, then state what the per-sub-channel arrangement buys that justifies the difference.
-
Using §10's device counts, compute the total
CAloading on an x8-based and an x16-based module, counting devices per signal. Relate the result to what 5.6 §4 says a buffer costs. -
§17's guard reports but does not block an access to an unconfigured sub-channel. Argue both sides, then state what a real device does and why the block's choice follows from it.
-
Construct the initialisation bug that produces
half_configuredon sub-channel 0 rather than 1, and describe the address-mapping condition under which a functional test would catch it by luck. -
Using §3's relation, find the two-cycle fraction at which the command bus and a 64-byte burst stream become co-limiting, stating the beat-rate assumption you must add and why §9's doubled beat count matters to it.
-
§6 declines to model 2N mode. State precisely what would have to be retrieved to model it, and what in §14's block would change if the common understanding described in that callout turned out to be correct.
25. Where This Goes
DDR5's command interface is now built rather than deferred. Fourteen CA bits with no strobe signals at all, commands over one or two cycles carrying fourteen or twenty-eight bits, CS_n inside the command code rather than beside it, one sampling instant per clock period, and two sub-channels each carrying an independent copy of all of it.
Six results carry forward. The per-sub-channel command bus is the load-bearing duplication, at exactly twice the command bandwidth per data bit of a shared bus — independent of the command mix, which makes it a property of the split rather than of the workload. And the granularity cancellation 4.6 §1 argued for is exact to the bit: sixteen beats of thirty-two bits and eight beats of sixty-four are both 512 bits, so the halved width cost nothing in transfer size and doubled the transfer's duration in beats.
The debt three chapters left here is paid — 7.1's encoder refuses DDR5 because DDR5 has no strobes to drive, and §14 is the block it declined to be. CS_n as part of the command code creates a framing hazard that corrupts only multi-cycle commands, so activates fail while reads succeed. A sub-channel is 32 bits with 4 check bits of its own, and the module width is the sum rather than a shared code. And the mode-register space is duplicated, which makes “half-configured” a state a controller needs a word for.
Two things left open, both recorded rather than glossed. 2N mode is named and not defined in the retrieved material, so §14's framer has no 2N mode and §6's callout says exactly what a reasonable expectation would be and why it is not a citation. And the core datasheet did not retrieve — the die addendum consulted explicitly supersedes parts of a document this module could not obtain, which is why no command truth table and no timing parameter appears in this chapter. That is the same gap 25.1 §19 reported and it has not narrowed.
What both chapters so far have assumed is that the module has power. Chapter 4.6 §3 establishes that DDR5 moved power regulation onto the module itself, and names the pressure: “regulating locally means the long path carries a higher voltage at a lower current”, and “it also makes each module responsible for its own supply quality.”
Chapter 25.3 takes up what that relocation actually is: a single bulk input becoming three device supplies and two low-dropout outputs, a power-good signal whose meaning is the conjunction of every regulated rail, a serial-presence-detect hub with its own thermal sensor, and the arithmetic — derivable from two verified voltages — that says why moving the regulator onto the module was worth doing at all.
Continue learning
Related tutorials
- Related topic
DDR5
A 16-beat burst on a 64-bit channel would double granularity. DDR5 halves the channel instead, into two independent 32-bit sub-channels — preserving access size exactly while doubling the number of independent request streams.
- Related topic
“DDR5 Is Just Faster DDR4”
The last of six, and the last chapter of the DDR track. DDR5 doubled the beats and halved the width each burst crosses, so access granularity is exactly unchanged — the structure moved in order to hold one number still, and zero of seventeen controller obligations are untouched.
- Related topic
Bank Groups
Not all bank pairs are equally independent. A bank group is the scope at which the internal column data path is shared, and the three-way classification of a request against its predecessor is the interface every later timing module consumes.
- Related topic
Ranks
A rank is the set of devices that together supply the controller's data width. Because ranks share one physical data bus, only one may drive it at a time — which makes a rank a question of resource ownership, and means adding ranks adds capacity without adding bandwidth.
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.
