Skip to content
VLSI Mentor

DDR · Module 3

Columns

Selecting a row makes a whole row's worth of data available. Column selection chooses the requested subset from it — a second, orthogonal selection with a completely different cost, a different relationship to state, and its own address field.

Chapter 3.1 established that selection is shared, that the set of cells one conductor reaches is a row, and that selecting a row makes an entire row's worth of data available whether anyone wanted that much or not.

It left an obvious gap. A requester asked for a few bytes. A row's worth of data is now available. How does the requested subset get chosen?

That is this chapter, and the answer is a second selection along the other axis — a column selection. The two are easy to confuse and completely different in almost every respect that matters:

Row selection is a physical act on the array. It consumes and re-establishes stored charge, takes real time, and leaves state behind. Column selection operates within an already-selected row. It touches no cells, costs comparatively little, and leaves no state.

Getting that distinction exactly right is the most valuable thing in the chapter, because nearly every misconception about DRAM performance is a failure to separate the two.

1. Two Selections, Not One

The array is a grid, and a grid needs two coordinates. That is the whole idea, and the rest of the chapter is its consequences.

Chapter 3.1 Fig 1 showed one horizontal conductor crossing many cells, each connecting down to its own vertical conductor. Assert the horizontal conductor and every cell along it participates — that is the row selection. What arrives at the bottom of the array is therefore one value per vertical conductor, all at once: a row's worth of data, side by side.

Now the second coordinate. Of all those values, the requester wanted a specific few. Choosing them is column selection, and it is a fundamentally different kind of operation:

A row address drives row selection, which connects one row of cells so that every vertical conductor carries one value. Those values are all available simultaneously. A column address then drives column selection, which chooses a subset of those values to pass to the data path, so the width leaving the device is far smaller than the width the row made available.Row addresswhich cells participateRow selectiona physical act on cellsRow's valuesall available at onceColumn addresswhich values leaveColumn selectionchooses a subsetData pathmuch narrowerdecodesenseddecodesubsetsource12
Figure 1 — two orthogonal selections: the row chooses which cells participate, the column chooses which of the resulting values leaves.

Read the figure as two independent decisions with a dependency. The top row decides which cells participate — a physical act. The bottom row decides which of the resulting values leaves — a routing act. The dashed edge is the dependency: column selection has nothing to choose from until row selection has produced it.

And note the width collapse on the right. A row makes a large number of values available simultaneously. What leaves through the data path is a small fraction of that. The array's internal width and its external width are very different numbers, and column selection is the mechanism that bridges them.

2. Why the Two Selections Are So Different

The distinction deserves a table, because every row of it is a consequence engineers use daily.

Row selectionColumn selection
Acts onthe cells themselvesvalues already available
Physical effectshares charge away; consumes stored statenone on the cells
Requires repair?yes — restoration (2.6)no
Leaves state?yes — the row stays selectedno
Exclusive?one row at a time per arrayseveral accesses in succession
Costsubstantial, and it is the expensive partcomparatively small
Repeatable cheaply?no — a different row means release and re-selectyes — within the selected row

The fourth and seventh rows are the ones that matter most. Because row selection leaves state and column selection does not, a sequence of accesses that stay within one row pays the expensive selection once and the cheap selection many times. A sequence that changes row on every access pays the expensive part every time.

That is the mechanical origin of everything the curriculum will later say about locality, address mapping and scheduling. It is not that DRAM "likes sequential access" as a preference — it is that the two selections have different costs and only one of them leaves reusable state.

3. Why the Address Has Separate Fields

Now the address itself, which follows directly.

The array needs two coordinates, and they are used at different times by different logic: the row coordinate drives row selection, the column coordinate drives the choice among the resulting values. So the address a device works with naturally decomposes into a row field and a column field — not as an encoding convenience, but because two physically distinct selections each need their own index.

Three observations, each of which prevents a specific error.

The fields are used at different moments. The row field is consumed when a row is selected; the column field is consumed when a subset is chosen. A request to an already-selected row uses only its column field — the row field is checked for a match and otherwise not acted on. The fields have different lifetimes, which is why a device interface can present them separately.

The field widths follow the array's dimensions, and the dimensions are a design decision with real consequences that 3.6 examines. More rows means a wider row field; more columns per row means a wider column field.

And which bits of a system address become which field is emphatically not settled here. That is the point §5 labours, because getting it wrong is one of the most consequential mistakes available in memory-system design.

4. Why the Data Path Is Narrower Than the Row

Worth a short section, because the width difference surprises people and its reason is purely economic.

A row makes many values available at once. The device's external interface carries far fewer signals than that. Why not widen the interface and read the whole row?

Because every external signal is expensive in a way internal ones are not. An external connection needs a pin on the package, a trace on the board, a driver and a receiver, and it consumes power every time it switches. Chapter 1.6 §5 established the coupling: capacity that cannot fit on the compute die must communicate across a package boundary, and that communication is paid for in pins, energy and signalling engineering. A device exposing a whole row's width would be dominated by its interface.

So the array is wide internally and narrow externally, and column selection is the reduction. Which produces a genuinely useful way to see the architecture: the array's internal parallelism is enormous and its external bandwidth is a deliberately narrow window onto it. A whole row is sensed and restored to deliver a small amount of data — and the only thing that makes this economical is reusing the selected row for further accesses, which is exactly what §2's table says column selection makes cheap.

5. RTL — Educational Address Decomposition

The problem being solved. Row and column selection each need an index, and something must produce both from an address. This is the smallest piece of the module's RTL progression and the one every later chapter's model depends on.

Abstraction level. Digital, and partly compile-time: the field widths are parameters and their legality is checked at elaboration.

What it models. A minimal split of an array-internal address into a row index and a column index, with parameter legality enforced and boundary behaviour defined.

What it deliberately does NOT model. It is not an address map. It does not decide which system address bits become row or column, does not involve channels, ranks, banks or bank groups, and implies no ordering of fields within a real address. It has no data path, no timing, and no notion of whether the row is currently selected — that is 3.1's tracker, combined with this in 3.6.

How to simulate it. As in 3.1 §5: vlog array_addr_decompose.sv tb_array_addr_decompose.sv then vsim -c tb_array_addr_decompose -do "run -all".

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// EDUCATIONAL ADDRESS DECOMPOSITION.  Classification: SYNTHESIZABLE RTL
// (the decomposition) + COMPILE-TIME checks (the parameter legality).
//
// This is NOT an address map. See the callout above: which SYSTEM address
// bits become row or column is Module 18's subject and is a performance
// decision, not a fact. This module only expresses that an array needs two
// indices and shows one way to produce them.
// ─────────────────────────────────────────────────────────────────────────
module array_addr_decompose #(
  parameter int ROWS = 8,
  parameter int COLS = 16,
  // DERIVED. Guards keep a dimension of 1 legal rather than producing a
  // zero-width index, which would be an illegal part-select below.
  parameter int ROW_W  = (ROWS <= 1) ? 1 : $clog2(ROWS),
  parameter int COL_W  = (COLS <= 1) ? 1 : $clog2(COLS),
  parameter int ADDR_W = ROW_W + COL_W
) (
  input  logic [ADDR_W-1:0] addr,

  output logic [ROW_W-1:0]  row_index,
  output logic [COL_W-1:0]  col_index,
  // High when the address selects a row or column that does not exist.
  // This can only happen when a dimension is NOT a power of two, and it is
  // an output rather than an assumption precisely because silently wrapping
  // an out-of-range index is how a design reads the wrong location while
  // believing it is right.
  output logic              addr_out_of_range
);

  // ── COMPILE-TIME legality. An illegal parameterisation is an elaboration
  //    error, not a runtime surprise. `$fatal` in an initial block inside a
  //    generate-if is the conventional way to express this.
  if (ROWS < 1 || COLS < 1) begin : g_bad_dims
    initial $fatal(1, "array_addr_decompose: ROWS and COLS must be >= 1");
  end

  // ── The decomposition itself. Column in the LOW bits so that consecutive
  //    addresses share a row -- a pedagogical choice, not a universal one.
  assign col_index = addr[COL_W-1:0];
  assign row_index = addr[ADDR_W-1:COL_W];

  // ── Range checking. With a power-of-two dimension every index value is
  //    legal and these comparisons are constant-folded away; with a
  //    non-power-of-two dimension they are the only thing standing between
  //    the design and an access to a location that does not exist.
  logic row_bad, col_bad;
  assign row_bad = (ROWS < (1 << ROW_W)) && (row_index >= ROW_W'(ROWS));
  assign col_bad = (COLS < (1 << COL_W)) && (col_index >= COL_W'(COLS));
  assign addr_out_of_range = row_bad || col_bad;

endmodule

Combinational decisions. Two part-selects and two range comparisons. There is no sequential state at all — decomposition is a pure function of the address, which is itself a useful fact: the fields are derived, never remembered.

Cycle-by-cycle example. With ROWS = 8 and COLS = 16, ROW_W is 3, COL_W is 4 and ADDR_W is 7. Address 7'b010_0011 gives row_index = 2, col_index = 3. Increment the address through 7'b010_1111 and the row index stays 2 while the column index sweeps — the cheap case of §2, visible directly in the decomposition. The next increment carries into the row field and the row changes, which is the expensive case.

Simulation expectations. A directed test sweeping every address with power-of-two dimensions should see addr_out_of_range low throughout and the two indices reconstructing the address exactly. With ROWS = 6 (not a power of two), addresses whose row field is 6 or 7 must assert addr_out_of_range.

Synthesis implications. Part-selects are wiring — no logic at all. The range comparisons synthesise to comparators, and for power-of-two dimensions the guard conditions are compile-time constant false, so the comparators are optimised away entirely. This is a genuinely free module in the common case, which is worth noticing: checking for an impossible condition costs nothing when the parameters make it impossible.

Corner cases, and why each guard exists. ROWS == 1 or COLS == 1 gives a width of 1 through the guard rather than $clog2(1) == 0, which would make addr[COL_W-1:0] an illegal part-select with a negative upper bound. Non-power-of-two dimensions make some index values unreachable, which is what addr_out_of_range reports. ROWS or COLS of zero is an elaboration error rather than a silently broken design.

Debugging observations. If the row index looks right and the column index is wrong by a power of two, suspect a field-width mismatch between this module and its consumer — the classic off-by-one-bit in a field boundary. If addr_out_of_range never asserts in a test that expects it, check whether the dimensions are powers of two, in which case the condition is genuinely unreachable and the test is wrong rather than the design.

Limitations. One array. Two fields. No bank, rank or channel. No mapping policy. Chapter 3.6 extends this to a hierarchical decomposition, and Module 18 owns the real question of which system bits go where.

6. Two Assertions Worth Writing

This module is small and mostly combinational, so its properties are correspondingly few — and writing only two is the right answer rather than a shortfall.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// VERIFICATION-ONLY. Bound to array_addr_decompose. Combinational module,
// so these are immediate-style checks in a clocked context supplied by the
// testbench -- there is no clock inside the module itself.

// P1 -- the decomposition is LOSSLESS. Reassembling the two indices must
// reproduce the address exactly. This is the property that catches a field
// boundary that is off by one bit, which is otherwise invisible: both
// indices look plausible and one of them is silently wrong.
property p_decomposition_is_lossless;
  @(posedge clk)
    addr == {row_index, col_index};
endproperty
assert property (p_decomposition_is_lossless);

// P2 -- an in-range report means both indices are genuinely in range. The
// converse of the useful direction: it is what lets a consumer treat
// !addr_out_of_range as permission to use the indices.
property p_in_range_means_usable;
  @(posedge clk)
    !addr_out_of_range |-> ((row_index < ROW_W'(ROWS)) || (ROWS == (1 << ROW_W)))
                        && ((col_index < COL_W'(COLS)) || (COLS == (1 << COL_W)));
endproperty
assert property (p_in_range_means_usable);

What these prove. P1 is the one that earns its place: a field-boundary error produces two plausible-looking indices, and only reassembly detects it. P2 gives a consumer a contract it can rely on — if the module says in range, the indices may be used.

What they do not prove. Nothing here says the decomposition is the right one for any system. A lossless, in-range decomposition can still map addresses in a way that destroys performance, and no assertion can detect that because it is not a correctness property. Address mapping quality is measured, not asserted — which is Module 18's subject and a genuinely different discipline from the one this chapter is teaching.

7. Verification Perspective

Boundary addresses are the whole game here. The first and last address; the last address of one row and the first of the next, which is where a field-boundary error shows up; every address when the dimensions are small enough to enumerate exhaustively, because exhaustive is achievable for a decomposition and is strictly better than random.

Non-power-of-two dimensions deserve their own tests. They are the only configuration in which addr_out_of_range can assert, and a regression that only ever elaborates power-of-two dimensions has never exercised the range logic at all. Test just-inside and just-outside for both fields.

Parameter configurations are part of the design. As Chapter 1.6 §4 argued, a parameterised module is several designs, and a regression that elaborates one has verified one. Include minimum dimensions (1), power-of-two, non-power-of-two, and asymmetric shapes.

Coverage targets. Every row index and every column index reached; the row-carry transition exercised; addr_out_of_range both asserted and not asserted; each parameter configuration elaborated.

And one thing that is explicitly not a verification target. Whether the mapping is good. That is a performance question answered by measurement against representative traffic, and confusing it with a correctness check is how teams end up believing a verified address map is a fast one.

8. Common Misconceptions

"A column selects one cell." Wrong model: column selection reaches into the array and reads a single cell. Engineering action: the engineer builds cost models in which a column access is a small physical operation on storage, and expects a column access to a closed row to be possible. Resulting bug: completely wrong performance reasoning, and a mental model with no room for why the row must be selected first or why a row conflict is expensive. Correct model: column selection chooses among values already made available by row selection. It touches no cells, and it is meaningless without a selected row. Prevention: remember the two verbs from §2 — row selection changes what is available, column selection changes what you take.

"Row and column are just two halves of an address." Wrong model: the split is an encoding convenience. Engineering action: treating the fields as interchangeable, and assuming that any bit could be in either field with no consequence beyond bookkeeping. Resulting bug: an address map chosen arbitrarily, which is one of the most expensive arbitrary choices available — the same workload can differ enormously in achieved bandwidth depending on which bits land in the row field. Correct model: the fields index two physically distinct selections with different costs and different relationships to state. Which system bits land in which field decides how often the expensive selection happens. Prevention: when reasoning about an address map, ask what it does to the rate of row changes. That is the quantity that matters.

"The mapping in a tutorial is how DRAM addressing works." Wrong model: column-in-low-bits is the DRAM convention. Engineering action: building a model or a test on the assumption, then generalising conclusions from it. Resulting bug: performance predictions that do not reproduce on real systems, and stimulus that exercises an access pattern the real device would never see. Correct model: the decomposition in §5 is pedagogical, chosen to make the cheap case visible. Real maps distribute bits across channel, rank, bank group, bank, row and column, and the distribution is a deliberate design decision. Prevention: the callout in §5, and the habit of asking whose mapping before trusting any address-field reasoning.

"Reading a column means only that column's cells participated." Wrong model: narrow access implies narrow work. Engineering action: cost models where a small read is small work, and stimulus that ignores the rest of the row. Resulting bug: the same error as 3.1 §10's third misconception, arriving through the column door: predictions that are optimistic in a way the engineer cannot explain. Correct model: the whole row participated during row selection. Column selection only chooses what leaves; it does not reduce the work already done. Prevention: separate "what was made available" from "what was taken". The first is a row's worth, always.

"A wider external interface would avoid the whole problem." Wrong model: the width reduction is an arbitrary limitation. Engineering action: proposing wider interfaces as an obvious fix for memory bandwidth, without pricing them. Resulting bug: an architecture proposal that cannot be built economically — every external signal costs a pin, a trace, a driver, a receiver and switching energy, and a device exposing a whole row's width would be dominated by its interface. Correct model: the array is deliberately wide internally and narrow externally, and column selection is the reduction. Widening is a real design axis with a real price, not a free improvement. Prevention: Chapter 1.6 §5's coupling — capacity that must live off-chip pays for every signal that crosses the boundary.

9. Debugging — Right Row, Wrong Data

Symptom. Reads return data from the correct row but the wrong position within it. Reproducible, no error reported, and the error is often a consistent offset.

Chapter 3.1 §11 ended its discrimination at exactly this point and handed it here, so start by confirming which side of that boundary the fault is on: is the row right? If the returned data belongs to a different row, that is 3.1's investigation. If the row is right and the position within it is wrong, continue below.

Mechanism 1 — a field boundary is off by one bit. Inspect: reassemble row_index and col_index and compare against the original address. Expected evidence: the error is a consistent power-of-two offset within the row, and it appears for every access rather than intermittently. Discriminator: P1 in §6 fails immediately in simulation. In a lab, the power-of-two signature is itself strongly diagnostic — arbitrary corruption does not produce it.

Mechanism 2 — the consumer and the decomposer disagree about field widths. Inspect: the parameters each module was elaborated with, not the parameters in the source. Expected evidence: correct behaviour for small addresses and wrong behaviour above a threshold that is a power of two — the point where the disputed bit first matters. Discriminator: the threshold behaviour. Mechanism 1 is wrong everywhere; this one is right until a specific magnitude.

Mechanism 3 — an out-of-range index was allowed to wrap. Inspect: whether the dimensions are powers of two, and whether addr_out_of_range is being consumed at all. Expected evidence: only certain high addresses fail, and they alias onto low locations. Discriminator: non-power-of-two dimensions plus an ignored range output is close to conclusive. Note the specific trap: a consumer that never checks the signal behaves identically to one that has no such signal.

Mechanism 4 — the column selection is right and the data path is misaligned. Inspect: whether the device returned the right subset and something after it rearranged the bytes. Expected evidence: all the right bytes present, in the wrong order or lanes. Discriminator: whether the correct data is present somewhere in the response. If it is, nothing was mis-selected and the fault is downstream of this chapter.

Mechanism 5 — the row was not what you think. Inspect: the tracker's open_row_id against the row field of the request. Expected evidence: plausible data from a consistently different row. Discriminator: this is 3.1's mechanism 1 or 4; it is on the list only because it can masquerade as a column fault when the two rows happen to hold similar data.

Discrimination in two cheap questions. Is the wrong data at a power-of-two offset within the row? Yes points at mechanisms 1 or 2, separated by whether the failure has a magnitude threshold. Are all the right bytes present but rearranged? Yes points at mechanism 4 and exonerates the selection entirely. Neither — check the range output and the dimensions.

The reasoning lesson. Field-decomposition bugs are unusually diagnosable because they leave arithmetic signatures: consistent power-of-two offsets, thresholds at bit boundaries, aliasing of high addresses onto low ones. An engineer who looks for those signatures identifies the fault in one observation, while an engineer who stares at the corrupted bytes has almost nothing to work with.

10. Interview Reasoning

"What is the difference between a row selection and a column selection?" Row selection is a physical act on the cells: it connects a whole row, consumes the stored charge, requires restoration, takes substantial time and leaves the row selected as persistent state. Column selection chooses among values that row selection has already made available: it touches no cells, needs no repair, costs comparatively little and leaves no state. A strong answer adds the consequence — because only one of them leaves reusable state, a sequence of accesses within one row pays the expensive selection once and the cheap one many times.

"Why does a DRAM address have separate row and column fields?" Because the array is a grid and needs two coordinates for two physically distinct selections, and the two are consumed at different moments: the row field when a row is selected, the column field when a subset is chosen. A request to an already-selected row uses only its column field, with the row field merely checked for a match. The fields have different lifetimes, which is also why a device interface can present them separately.

"Does it matter which system address bits become the row field?" Enormously, and this is where a shallow answer shows. The choice determines how often consecutive accesses land in the same row, and therefore how often the expensive selection is paid. The same workload can achieve very different bandwidth under different maps, and real maps spread bits across channel, rank, bank group, bank, row and column deliberately. It is a performance decision rather than an encoding detail — and a correct, lossless map can still be a bad one.

"Why is a device's external data width so much narrower than a row?" Because external signals are expensive in a way internal ones are not: each needs a pin, a board trace, a driver, a receiver and switching energy, so a device exposing a whole row's width would be dominated by its interface. The array is therefore deliberately wide internally and narrow externally, with column selection as the reduction — and the only thing making that economical is reusing the selected row for further accesses.

"Can a column access happen without a row being selected?" No, and the reason is structural rather than a protocol rule. Column selection chooses among values that row selection produced; with no row selected there is nothing to choose from. This is why access sequencing has prerequisites at all, and 3.5 sharpens it further — a row being selected is not yet sufficient, because the values are not available until sensing has resolved them.

11. Engineering Check

An educational array has ROWS = 8 and COLS = 16, decomposed as in §5 with the column index in the low bits. A requester issues reads at addresses 0x20, 0x21, 0x22, then 0x35.

1. Decompose each address. COL_W is 4 and ROW_W is 3, so the low four bits are the column. 0x20 is 010_0000: row 2, column 0. 0x21: row 2, column 1. 0x22: row 2, column 2. 0x35 is 011_0101: row 3, column 5.

2. Classify the four accesses by cost. The first selects row 2 — the expensive operation. The second and third are column selections within the already-selected row 2 — cheap. The fourth needs row 3, so row 2 must be released and row 3 selected before it can be served — expensive again. Three cheap-or-expensive decisions, driven entirely by the row field.

3. Why did three consecutive addresses share a row? Because the mapping puts the column index in the low bits, so incrementing the address sweeps columns before carrying into the row field. That is the pedagogical choice of §5 — and it is exactly why the example is easy to reason about.

4. What would change if the fields were swapped — row in the low bits? Every consecutive address would land in a different row, so all four accesses would be expensive. Same array, same requests, same correct decomposition, dramatically worse behaviour. This is the clearest possible demonstration that a lossless address map can still be a bad one, and why Module 18 exists.

5. Is the swapped mapping ever the right choice? It can be, and that is the honest and interesting answer. Spreading consecutive addresses across different rows — or across banks and channels, which a real map can do — distributes traffic instead of concentrating it, and for some access patterns and some device organisations that is worth more than row reuse. The right map depends on the traffic and on the device's parallelism, which is precisely why it is a measured design decision rather than a convention.

6. What would you instrument to tell whether a map is working? The row-change rate per access, and the number of column accesses served per row selection. Those two numbers say directly how well the map and the traffic fit each other — and they are the measurement 3.6 builds the classification for.

12. Summary

An array is a grid, so it needs two selections. Row selection connects a whole row of cells and makes a row's worth of values available simultaneously. Column selection chooses the requested subset from those values — a second, orthogonal selection along the other axis.

The two are different in almost every respect that matters. Row selection acts on the cells: it shares their charge away, requires restoration, takes substantial time, and leaves the row selected as state. Column selection acts on values that are already available: it touches no cells, needs no repair, costs comparatively little, and leaves no state. Row selection changes what is available; column selection changes what you take.

Because only row selection leaves reusable state, a sequence of accesses within one row pays the expensive selection once and the cheap one many times, while a sequence that changes row pays the expensive part every time. That asymmetry is the mechanical origin of everything the curriculum later says about locality, mapping and scheduling.

The address therefore decomposes into two fields, consumed at different moments by different logic — the row field when a row is selected, the column field when a subset is chosen. The decomposition is lossless arithmetic and costs essentially nothing; the field boundary is where the subtle bugs live, and they announce themselves with power-of-two offsets and bit-boundary thresholds.

And the data path is far narrower than a row because every external signal costs a pin, a trace, a driver, a receiver and switching energy. The array is deliberately wide internally and narrow externally, with column selection as the reduction — which is only economical because the selected row can be reused.

But which system address bits become which field is not settled by any of this. That is a performance decision with large consequences, owned by Module 18, and a lossless map can still be a bad one.

13. What Comes Next

Two axes are now established, and both have been described in terms of what they select. Neither has been described in terms of what the conductors actually are.

Chapter 3.3 takes the vertical one. It is the conductor a column is named for, it is shared by many cells, and it is where the electrical difficulty of the whole technology concentrates: its capacitance dwarfs a cell's, which is why the read signal is small, why sensing is hard, and why the length of that conductor is one of the central architectural trade-offs in a DRAM array. It is also the chapter with the least RTL in the module, for a reason it states plainly — charge sharing on a shared conductor is not something digital logic represents.

Return to Rows for the first selection axis, Restore Operations for why a selected row's values are available at all, or Cost vs Density for the pin-and-package economics behind the narrow data path. 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.