DDR · Module 26
Through-Silicon Vias (TSVs)
At a per-via reliability of one in ten thousand, a twelve-high stack works 2.5% of the time. One spare per group of 64 removes 99.7% of that risk — and 48 spares tolerate between 2 and 48 failures.
Chapter 26.2 §3 counted positions on a die's face and 26.2 §11 drew microbumps joining a stack to an interposer. Neither said what passes through a die to reach the one above it.
Chapter 4.8 §1 describes it in one clause — “conductors passing vertically through the body of each die” — and 26.1 §1 identifies it as the mechanism that changes the scaling exponent from 4/L to a constant. This chapter is about what that costs.
The answer is not what the geometry suggests. Chapter 26.2 §5 found that connections are geometrically abundant — a 1024-bit field uses about 2% of a die's face. What is scarce is the probability that all of them work. §5 quantifies that, §6 quantifies the fix, and the fix turns out to be startlingly cheap.
1. How Many a Stack Needs
Start with the count, because everything else in the chapter is a function of it.
CURRICULUM-DERIVED from 4.8 §2: the interface is 1024 bits. Chapter 26.2 §6 established that signals are not the whole count — power and ground need connections too, and the overhead ratio must be supplied rather than guessed.
DERIVED, at ILLUSTRATIVE overhead ratios:
| Signal : power/ground | Conductors for the interface |
|---|---|
| 1 : 1 | 2,048 |
| 1 : 2 | 3,072 |
So the vertical path carries a few thousand conductors — the same order as 26.2 §6's bump field, which is unsurprising since they carry the same interface.
What is not yet determined is how that count varies with height, and there are two plausible answers.
2. Bussed or Dedicated
Two stated models, both consistent with everything this module verified, and the choice between them changes the count dramatically.
Model A — bussed. One set of conductors runs the full height of the stack, and every die taps the set it needs. The count per die is the interface count, constant with height.
Model B — dedicated. Each die has its own conductors down to the base. A die at level k needs its own set, and every set below it must pass through. The count grows downward.
DERIVED at a 1:2 ratio (3,072 conductors), recomputed:
| Stack height | Model A, every die | Model B, bottom die | Model B, top die |
|---|---|---|---|
| 2 | 3,072 | 6,144 | 3,072 |
| 4 | 3,072 | 12,288 | 3,072 |
| 8 | 3,072 | 24,576 | 3,072 |
| 12 | 3,072 | 36,864 | 3,072 |
Under model B the bottom die carries N times what the top die carries — twelve times, at full height.
That asymmetry is the argument against model B, and it is structural rather than electrical. A die whose via count depends on how many dies sit above it is not the same die at different stack heights — so a manufacturer would need a different bottom die for every height, and 26.2 §8 established that stacking is valuable precisely because it combines ordinary dies.
Model A keeps every core die identical. One die design, four stack heights, and the count does not grow. So the structural argument favours bussing decisively, and §13's block supports both models only because this module could not verify which is used.
3. A Path to the Top Passes Through Everything Below
Here is the structural property that makes a vertical connection different in kind from a planar one, and it holds under both of §2's models.
A conductor reaching die k must physically pass through dies 1 through k−1. There is no route that skips a die — the path goes through the body of each one.
So a via is a serial resource with a shared failure mode. DERIVED, counting traversals:
| Stack height | Vias traversed reaching die k | Total traversals to reach every die |
|---|---|---|
| 4 | k, for k = 1…4 | 10 |
| 8 | k | 36 |
| 12 | k | 78 |
The totals are N(N+1)/2, recomputed: 12 × 13 / 2 = 78.
Two consequences, and the second is the one that drives the rest of the chapter.
A defect low in the stack is worse than a defect high in it. A broken via in the bottom die's set breaks connectivity to every die above; the same defect in the top die breaks connectivity to one. The consequences of identical defects are not identical, which is a property no planar interface has.
And the number of things that must all work grows with both the count and the height. A planar interface needs its connections to work. A stack needs its connections to work at every level they pass through — and §5 is about what that multiplication does.
4. Why a Stack Needs Spares
A planar interface with a few thousand connections does not normally carry spare connections. A stack does. The reason is arithmetic, and it is worth doing because the magnitude is not intuitive.
The model, stated. Each via works independently with probability y. A stack of N dies carrying T conductors per die needs all T × N of them. With no repair, the stack works with probability y^(T×N).
DERIVED at ILLUSTRATIVE reliabilities, 3,072 conductors per die, recomputed:
Per-via reliability y | Height 8 — 24,576 vias | Height 12 — 36,864 vias |
|---|---|---|
| 0.999999 | 0.9757 | 0.9638 |
| 0.99999 | 0.7821 | 0.6917 |
| 0.9999 | 0.0856 | 0.0251 |
Read the last row. A per-via reliability of 0.9999 — one failure in ten thousand, which sounds excellent — gives a 12-high stack a 2.5% chance of working. Ninety-seven and a half percent of stacks would be scrap.
That is the whole case for redundancy, and it is not a marginal one. Three observations:
“Excellent” per-connection reliability is not a meaningful standard here. The figure that matters is y^(T×N), and with T×N in the tens of thousands, the exponent destroys anything short of near-perfection.
Height makes it worse, monotonically. Every row degrades from height 8 to height 12 — which means the stack heights 4.8 §2 publishes are not equally easy, and the tallest is the hardest by a wide margin.
And one more nine buys an enormous amount. Moving y from 0.9999 to 0.99999 takes the 12-high figure from 0.025 to 0.69 — a factor of 27.6. So the sensitivity to per-via reliability is extreme, which cuts both ways: improvement is highly leveraged and degradation is catastrophic.
5. One Spare Does Almost All the Work
The fix is cheaper than the problem suggests, and this is the chapter's best result.
The model, stated. Conductors are organised into groups of G, each group carrying S spares. A group survives up to S failures. With per-via failure probability q, a group works with probability P(at most S failures).
DERIVED at a group of 64, ILLUSTRATIVE q, recomputed:
Spares S | q = 10⁻⁴ | q = 10⁻⁵ |
|---|---|---|
| 0 | 0.993620 | 0.999360 |
| 1 | 0.999980 | 0.9999998 |
| 2 | 0.99999996 | ~1 |
| 4 | ~1 | ~1 |
One spare in sixty-four takes the group's failure probability from 6.4 × 10⁻³ to 2.0 × 10⁻⁵ at q = 10⁻⁴ — a reduction of about 318×, removing 99.7% of the risk.
DERIVED, and the shape is worth naming: the first spare is worth more than every subsequent spare combined.
| Spares | Failure probability at q = 10⁻⁴ | Improvement over previous |
|---|---|---|
| 0 | 6.38 × 10⁻³ | — |
| 1 | 2.01 × 10⁻⁵ | 318× |
| 2 | 4.2 × 10⁻⁸ | 478× |
| 4 | negligible | — |
So redundancy in a stack is a steep, cheap, early win. A 1.6% overhead — one spare per 64 — buys more than two orders of magnitude, which is why spare connections are universal in stacked memory and absent from planar interfaces where the exponent is small enough not to need them.
And it explains why the repair machinery exists at all. Spares are useless without a mechanism to use them: something must detect which conductor failed and redirect its traffic. §7 is that mechanism, and §14 builds it.
6. Repair Is Per Group, Not Per Via
A spare cannot substitute for an arbitrary failed conductor. It can substitute for one in its own group — and that constraint shapes everything about how repair works.
Why grouping is necessary. A spare that could replace any conductor anywhere would need a path to every position, which is a full crossbar over thousands of signals. The routing would cost more than the redundancy saves. A spare confined to a group of 64 needs a path to 64 positions, which is tractable.
What grouping costs. Failures are only tolerable where the spares are. Two failures in one group with one spare is unrepairable, even if every other group's spare is unused. DERIVED under §6's model, q = 10⁻⁴, 48 groups of 64 with one spare each:
spares available in total 48
failures the stack tolerates depends entirely on their DISTRIBUTION
worst case 2 failures, if both land in one group
best case 48 failures, one per groupSo the repairable failure count is between 2 and 48 for the same 48 spares, and which one you get is a property of where the defects are rather than how many there are.
That is why §6's independence assumption matters more here than in §5. Independent failures spread out; clustered failures concentrate. A clustered defect mechanism defeats grouped redundancy in a way it does not defeat ungrouped redundancy — and real defects do cluster. This chapter cannot say how much, and the direction is unambiguous.
7. The Repair Map Has to Live Somewhere
Spares and grouping are structure. Using them requires state: a record of which conductor in each group has been replaced by the spare.
That record is a repair map, and three things about it follow from what it must do.
It must be applied to every access, not consulted occasionally. A repaired conductor is repaired permanently; every transfer must be routed according to the map. So the map is in the data path rather than beside it.
It must survive power loss. A repair discovered at manufacture must still be in force after a power cycle, which means it is stored in something non-volatile and loaded before the interface is used.
And it must be established before the interface is trusted. Chapter 26.2 §9 establishes that the base die is the only die with an external interface and is shared by every core die — which makes it the only place the map can be applied for the whole stack.
8. Assembly Is Ordered
The last structural property, and the one that makes a state machine appropriate.
A stack is built from the bottom up. A die cannot be placed on a die that is not yet there, and once a die is placed the ones below it are no longer accessible. So assembly is a strictly ordered sequence with no revisiting — which is a different constraint from anything in a planar flow.
Three consequences.
A defect found after placement cannot be fixed by replacing the die. The dies below are committed. Chapter 4.8 §3 owns the cost: “a defect in any of them scrapes an assembly containing an expensive processor.”
So testing must happen before placement. A die proven good before it joins the stack is the only way to keep the expected cost of assembly bounded — and that is the known-good-die requirement, which is a consequence of the ordering rather than an independent policy.
And the test must exercise the vertical connections, which only exist once the die is placed. That is the tension at the heart of stack assembly: the connections that most need testing cannot be tested until placement, and placement is the step that cannot be undone. §15's guard enforces the ordering; it cannot resolve the tension, and neither can anything else in this chapter.
9. The Vertical Path as a Structure
Two things the structure shows.
Height feeds three different things and only one of them is capacity. It drives the bottom die's count under the dedicated model, the traversal count, and the assembly ordering — and 26.2 §8 established it also drives capacity while leaving bandwidth untouched. So height is the parameter that makes a stack harder in every dimension except the one it buys.
And Bottom die count is the only node drawn in the danger variant alongside Assembly order. Both are places where the stack's verticality costs something a planar part never pays: an asymmetric die under the dedicated model, and an irreversible build sequence. §2 argues the first away by choosing bussing; nothing argues the second away.
10. The Count Budget
// ---------------------------------------------------------------------
// tsv_count_budget -- conductors per die under a bussed or a
// dedicated organisation, and the traversal count a height implies.
//
// CLASSIFICATION: educational, synthesisable. COUNTING ONLY.
//
// WHAT IT DOES NOT MODEL -- the most important part of this header:
// - capacitance, resistance, inductance. A via has all three and
// NONE is computed here.
// - thermal resistance, temperature, mechanical stress, aspect
// ratio, keep-outs, placement rules.
// - YIELD, and no probability of any kind. §5 and §6's arithmetic
// is PROSE, computed outside the design, precisely so that no
// synthesisable block appears to predict a yield.
// - electrical loading. §2's callout notes every die on a bussed
// set presents a load, and says plainly that this chapter cannot
// quantify it.
// A VIA IS AN INTEGER HERE. Converting one into an electrical result
// would be this module's fake-eye failure (22.3).
//
// BOTH MODELS ARE SUPPORTED because §2 could not establish which is
// used. The structural argument favours bussing; the parameter
// exists so the open question is visible rather than assumed away.
// ---------------------------------------------------------------------
module tsv_count_budget #(
parameter int SIGNALS = 1024, // CURRICULUM-DERIVED (4.8 §2)
// §1: power/ground per signal, as a ratio so no real is needed.
parameter int PG_NUM = 2,
parameter int PG_DEN = 1,
parameter int CORE_DIES = 12, // CURRICULUM-DERIVED set (4.8 §2)
parameter bit DEDICATED = 1'b0, // 0 = bussed (§2 model A)
parameter int DIE_IDX_W = $clog2(CORE_DIES),
// A COUNT of dies up to CORE_DIES needs clog2(CORE_DIES+1).
parameter int DIECNT_W = $clog2(CORE_DIES + 1),
parameter int CNT_W = 32
) (
input logic clk,
input logic rst_n,
// ── Query a die level, counted from the bottom (0 = lowest core).
input logic q_valid,
input logic [DIE_IDX_W-1:0] q_die,
output logic [CNT_W-1:0] q_conductors,
// §3: a path to this die traverses this many die bodies.
output logic [DIECNT_W-1:0] q_traversals,
output logic q_in_range,
// ── The organisation, published.
output logic [CNT_W-1:0] conductors_per_interface,
output logic [CNT_W-1:0] conductors_bottom_die,
output logic [CNT_W-1:0] conductors_top_die,
output logic [CNT_W-1:0] total_traversals,
output logic is_bussed,
// §2: under a dedicated model the bottom die carries N times the
// top. Published as a PAIR so no division happens here.
output logic [CNT_W-1:0] asymmetry_num,
output logic [CNT_W-1:0] asymmetry_den,
output logic err_die_out_of_range,
output logic any_error
);
// §1: signals plus overhead, without a real.
localparam int IFACE = (SIGNALS * (PG_DEN + PG_NUM)) / PG_DEN;
// §2: bussed is constant; dedicated grows downward.
localparam int BOTTOM = DEDICATED ? (IFACE * CORE_DIES) : IFACE;
localparam int TOP = IFACE;
// §3: N(N+1)/2 traversals to reach every die.
localparam int TRAV_TOTAL = (CORE_DIES * (CORE_DIES + 1)) / 2;
initial begin
if (SIGNALS < 1)
$fatal(1, "tsv_count_budget: SIGNALS must be at least 1");
if (PG_DEN < 1 || PG_NUM < 0)
$fatal(1, "tsv_count_budget: overhead must be non-negative over a positive denominator");
if (!(CORE_DIES inside {2, 4, 8, 12}))
// CURRICULUM-DERIVED height set (4.8 §2). A height outside it
// is refused rather than accommodated, because accommodating
// it would imply a generality the source does not support.
$fatal(1, "tsv_count_budget: CORE_DIES must be 2, 4, 8 or 12, got %0d", CORE_DIES);
if (IFACE < SIGNALS)
// §1: the overhead must actually be counted.
$fatal(1, "tsv_count_budget: interface count (%0d) below signal count (%0d)",
IFACE, SIGNALS);
if (DIECNT_W <= DIE_IDX_W && CORE_DIES > 1)
// COUNT vs INDEX. Too narrow and the traversal count for the
// top die cannot be represented, which silently understates
// the deepest path -- the flattering direction.
$fatal(1, "tsv_count_budget: DIECNT_W too narrow to count CORE_DIES");
if (BOTTOM > ((1 << (CNT_W - 1)) - 1))
$fatal(1, "tsv_count_budget: CNT_W too narrow for the bottom-die count");
// §2's asymmetry identity: bussed means symmetric, dedicated
// means the bottom carries exactly CORE_DIES times the top.
if (!DEDICATED && (BOTTOM != TOP))
$fatal(1, "tsv_count_budget: a bussed organisation must be symmetric");
if (DEDICATED && (BOTTOM != TOP * CORE_DIES))
$fatal(1, "tsv_count_budget: dedicated bottom count must be CORE_DIES x top");
end
logic e_range;
// Declared above the continuous assigns that read it.
logic in_rng;
assign in_rng = q_valid &&
({{(32-DIE_IDX_W){1'b0}}, q_die} < 32'(CORE_DIES));
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) e_range <= 1'b0;
else e_range <= q_valid && !in_rng;
end
// Under the dedicated model a die at level d carries its own set
// plus every set passing through it to the dies above.
logic [CNT_W-1:0] dedicated_at_d;
assign dedicated_at_d = CNT_W'(IFACE) *
(CNT_W'(CORE_DIES) - {{(32-DIE_IDX_W){1'b0}}, q_die});
assign q_conductors = DEDICATED ? dedicated_at_d : CNT_W'(IFACE);
// §3: reaching die d traverses d+1 die bodies, counting from 0.
assign q_traversals = DIECNT_W'({{(32-DIE_IDX_W){1'b0}}, q_die} + 32'd1);
assign q_in_range = in_rng;
assign conductors_per_interface = CNT_W'(IFACE);
assign conductors_bottom_die = CNT_W'(BOTTOM);
assign conductors_top_die = CNT_W'(TOP);
assign total_traversals = CNT_W'(TRAV_TOTAL);
assign is_bussed = !DEDICATED;
assign asymmetry_num = CNT_W'(BOTTOM);
assign asymmetry_den = CNT_W'(TOP);
assign err_die_out_of_range = e_range;
assign any_error = e_range;
endmoduleThe asymmetry is published as a pair rather than a ratio, so a caller reads BOTTOM over TOP and draws its own conclusion. Under bussing the pair is equal and under the dedicated model it is N to 1 — and publishing it as two numbers rather than one makes §2's structural argument visible in the interface rather than requiring the reader to remember it.
11. The Repair Map
// ---------------------------------------------------------------------
// tsv_repair_map -- which conductor in each group has been replaced
// by that group's spare, and what happens when a group runs out.
//
// CLASSIFICATION: educational, synthesisable.
//
// WHAT IT DOES NOT MODEL:
// - any physical via, defect mechanism, probability or yield.
// §5 and §6's arithmetic is prose for exactly this reason.
// - any real device's repair mechanism. §8's callout: 4.8 deferred
// "the initialisation and test infrastructure" to this module and
// this module could not obtain it. This is the SHAPE §6 requires,
// not a specification.
// - non-volatile storage, or when a map is loaded. §8 argues both
// are necessary and neither is modelled.
//
// WHY EXHAUSTION IS AN OUTPUT AND NOT AN ERROR: §6. Two failures in
// one group with one spare is unrepairable, and that is a property of
// where the defects landed rather than a fault in this logic. The
// block reports it; it cannot fix it.
// ---------------------------------------------------------------------
module tsv_repair_map #(
parameter int GROUPS = 48,
parameter int PER_GROUP = 64, // ILLUSTRATIVE (§6)
parameter int SPARES_PER_GRP = 1, // ILLUSTRATIVE (§5)
// Ternary-guarded, matching 26.1's hbm_channel_organisation. A bare
// $clog2(GROUPS) gives 0 at GROUPS=1, and [0-1:0] is an illegal
// width -- the same sibling-block guard inconsistency Module 24
// shipped once between two blocks doing the same job.
parameter int GRP_IDX_W = (GROUPS <= 1) ? 1 : $clog2(GROUPS),
parameter int MEM_IDX_W = (PER_GROUP <= 1) ? 1 : $clog2(PER_GROUP),
// COUNTS need one more bit than the matching index.
parameter int GRPCNT_W = $clog2(GROUPS + 1),
parameter int SPRCNT_W = $clog2(SPARES_PER_GRP + 1),
parameter int TOTCNT_W = $clog2(GROUPS * SPARES_PER_GRP + 1)
) (
input logic clk,
input logic rst_n,
// ── Report a failed conductor. Repair is attempted immediately.
input logic fail_valid,
input logic [GRP_IDX_W-1:0] fail_group,
input logic [MEM_IDX_W-1:0] fail_member,
// ── Verdict for this report.
output logic repaired,
output logic unrepairable,
// ── Map state.
output logic [SPRCNT_W-1:0] spares_used [GROUPS],
output logic [TOTCNT_W-1:0] total_spares_used,
output logic [TOTCNT_W-1:0] total_spares,
output logic [GRPCNT_W-1:0] groups_exhausted,
output logic stack_repairable,
// §6: the same spare count tolerates a different number of
// failures depending on their distribution. Both bounds published,
// because the count alone does not say which applies.
output logic [TOTCNT_W-1:0] tolerable_best_case,
output logic [SPRCNT_W-1:0] tolerable_worst_case,
output logic err_group_out_of_range,
output logic err_member_out_of_range,
output logic err_double_repair,
output logic any_error
);
initial begin
if (GROUPS < 1 || PER_GROUP < 2)
$fatal(1, "tsv_repair_map: need at least one group of at least two members");
if (SPARES_PER_GRP < 1)
// A group with no spare is a group with no repair, which makes
// the whole block a counter. §5 establishes the first spare is
// worth more than all the rest, so zero is the one value that
// defeats the mechanism entirely.
$fatal(1, "tsv_repair_map: SPARES_PER_GRP must be at least 1");
if (SPARES_PER_GRP >= PER_GROUP)
$fatal(1, "tsv_repair_map: spares must be fewer than the group's members");
if (GRPCNT_W <= GRP_IDX_W && GROUPS > 1)
$fatal(1, "tsv_repair_map: GRPCNT_W too narrow to count GROUPS");
if (TOTCNT_W < $clog2(GROUPS * SPARES_PER_GRP + 1))
$fatal(1, "tsv_repair_map: TOTCNT_W too narrow for the total spare count");
end
logic [SPRCNT_W-1:0] used [GROUPS];
// One repair record per group per spare: which member it replaced.
logic [MEM_IDX_W-1:0] repl [GROUPS][SPARES_PER_GRP];
logic [SPARES_PER_GRP-1:0] slot_taken [GROUPS];
logic e_grp, e_mem, e_dbl, rep_q, unrep_q;
// Declared above the continuous assigns that read them.
logic grp_ok, mem_ok;
assign grp_ok = ({{(32-GRP_IDX_W){1'b0}}, fail_group} < 32'(GROUPS));
assign mem_ok = ({{(32-MEM_IDX_W){1'b0}}, fail_member} < 32'(PER_GROUP));
logic [TOTCNT_W-1:0] tot_used;
logic [GRPCNT_W-1:0] n_exh;
always_comb begin
tot_used = '0;
n_exh = '0;
for (int g = 0; g < GROUPS; g++) begin
tot_used = tot_used + TOTCNT_W'(used[g]);
if (used[g] == SPRCNT_W'(SPARES_PER_GRP)) n_exh = n_exh + GRPCNT_W'(1);
end
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for (int g = 0; g < GROUPS; g++) begin
used[g] <= '0;
slot_taken[g] <= '0;
for (int s = 0; s < SPARES_PER_GRP; s++) repl[g][s] <= '0;
end
e_grp <= 1'b0; e_mem <= 1'b0; e_dbl <= 1'b0;
rep_q <= 1'b0; unrep_q <= 1'b0;
end else begin
e_grp <= 1'b0; e_mem <= 1'b0; e_dbl <= 1'b0;
rep_q <= 1'b0; unrep_q <= 1'b0;
if (fail_valid) begin
if (!grp_ok) begin
e_grp <= 1'b1;
end else if (!mem_ok) begin
e_mem <= 1'b1;
end else begin
begin : attempt
logic already;
logic [SPRCNT_W-1:0] u;
already = 1'b0;
u = used[fail_group];
// Already repaired: reported, and NOT re-assigned. A
// second spare for the same member would consume a spare
// that another member may need.
for (int s = 0; s < SPARES_PER_GRP; s++)
if (slot_taken[fail_group][s] && (repl[fail_group][s] == fail_member))
already = 1'b1;
if (already) begin
e_dbl <= 1'b1;
end else if (u == SPRCNT_W'(SPARES_PER_GRP)) begin
// §6: the group is out of spares. Not an error in this
// logic -- a property of where the defects landed.
unrep_q <= 1'b1;
end else begin
// Take the lowest free slot. Which slot is irrelevant;
// that one is taken is what matters.
for (int s = 0; s < SPARES_PER_GRP; s++)
if (!slot_taken[fail_group][s] && (SPRCNT_W'(s) == u)) begin
slot_taken[fail_group][s] <= 1'b1;
repl[fail_group][s] <= fail_member;
end
used[fail_group] <= u + SPRCNT_W'(1);
rep_q <= 1'b1;
end
end
end
end
end
end
assign repaired = rep_q;
assign unrepairable = unrep_q;
assign spares_used = used;
assign total_spares_used = tot_used;
assign total_spares = TOTCNT_W'(GROUPS * SPARES_PER_GRP);
assign groups_exhausted = n_exh;
// The stack is repairable while no group has been asked for more
// than it has. Exhaustion is permanent for that group.
assign stack_repairable = (n_exh == '0);
// §6: the same total tolerates a range depending on distribution.
assign tolerable_best_case = TOTCNT_W'(GROUPS * SPARES_PER_GRP);
assign tolerable_worst_case = SPRCNT_W'(SPARES_PER_GRP);
assign err_group_out_of_range = e_grp;
assign err_member_out_of_range = e_mem;
assign err_double_repair = e_dbl;
assign any_error = e_grp | e_mem | e_dbl;
endmoduletolerable_best_case and tolerable_worst_case are published together and they differ by a large factor. §6 established that 48 spares tolerate between 2 and 48 failures depending on distribution — so a single “spares available” figure would be misleading in whichever direction the reader guessed. Publishing both makes the dependence on distribution part of the interface.
And unrepairable is a verdict rather than an error. A group asked for a second spare it does not have has exhausted a resource, which §6 explains is a property of where defects landed. The logic did nothing wrong, so any_error stays low — and conflating the two would make a distribution problem look like a design fault.
12. The Assembly Guard
// ---------------------------------------------------------------------
// stack_assembly_guard -- dies join the stack bottom-up, only after
// passing test, and never again once placed.
//
// CLASSIFICATION: educational, synthesisable.
//
// WHAT IT DOES NOT MODEL:
// - any physical assembly step, bonding, alignment or thermal
// processing
// - what a test DOES. known_good is an input. §9's tension --
// that the vertical connections needing test do not exist until
// placement -- is real and this block cannot resolve it.
// - yield or cost. 4.8 §3 owns the argument that a late defect
// scraps an expensive assembly; no block computes it.
//
// THE FORBIDDEN TRANSITIONS ARE THE POINT (§9): a die may not join
// out of order, may not join untested, and a placed die may not be
// replaced. Asserting the legal sequence alone would also hold for a
// machine that permitted all three.
// ---------------------------------------------------------------------
module stack_assembly_guard #(
parameter int CORE_DIES = 12, // CURRICULUM-DERIVED (4.8 §2)
parameter int LEVELS = CORE_DIES + 1, // plus the base die (26.2 §7)
parameter int LVL_IDX_W = $clog2(LEVELS),
parameter int LVLCNT_W = $clog2(LEVELS + 1),
parameter int CNT_W = 16
) (
input logic clk,
input logic rst_n,
// ── Attempt to place a die at a level.
input logic place_valid,
input logic [LVL_IDX_W-1:0] place_level,
input logic known_good,
// ── State.
output logic [LVLCNT_W-1:0] levels_placed,
output logic [LVL_IDX_W-1:0] next_level,
output logic stack_complete,
output logic place_accepted,
// ── Accounting.
output logic [CNT_W-1:0] cnt_accepted,
output logic [CNT_W-1:0] cnt_rejected,
// ── FORBIDDEN, each by kind so a debug session learns which rule
// was broken rather than that one was.
output logic err_out_of_order,
output logic err_not_known_good,
output logic err_already_placed,
output logic err_place_after_complete,
output logic any_violation
);
initial begin
if (!(CORE_DIES inside {2, 4, 8, 12}))
$fatal(1, "stack_assembly_guard: CORE_DIES must be 2, 4, 8 or 12");
if (LEVELS != CORE_DIES + 1)
// 26.2 §7: exactly one base die, and it is level 0.
$fatal(1, "stack_assembly_guard: LEVELS must be CORE_DIES + 1");
if (LVLCNT_W <= LVL_IDX_W && LEVELS > 1)
// COUNT vs INDEX: too narrow and stack_complete is unreachable.
$fatal(1, "stack_assembly_guard: LVLCNT_W too narrow to count LEVELS");
end
logic [LVLCNT_W-1:0] placed;
logic [CNT_W-1:0] n_ok, n_no;
logic e_ord, e_kgd, e_dup, e_aft, acc;
// Declared above the continuous assigns that read them.
logic complete_now;
assign complete_now = (placed == LVLCNT_W'(LEVELS));
logic is_next;
assign is_next = ({{(32-LVL_IDX_W){1'b0}}, place_level} == 32'(placed));
logic below_placed;
assign below_placed = ({{(32-LVL_IDX_W){1'b0}}, place_level} < 32'(placed));
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
placed <= '0; n_ok <= '0; n_no <= '0; acc <= 1'b0;
e_ord <= 1'b0; e_kgd <= 1'b0; e_dup <= 1'b0; e_aft <= 1'b0;
end else begin
acc <= 1'b0;
e_ord <= 1'b0; e_kgd <= 1'b0; e_dup <= 1'b0; e_aft <= 1'b0;
if (place_valid) begin
if (complete_now) begin
// §9: a placed stack is not revisited. Placing onto a
// complete stack is refused rather than growing it beyond
// its verified height.
e_aft <= 1'b1;
if (n_no != {CNT_W{1'b1}}) n_no <= n_no + CNT_W'(1);
end else if (below_placed) begin
// A level below the frontier already holds a die, and the
// dies above it are committed. Replacement is impossible.
e_dup <= 1'b1;
if (n_no != {CNT_W{1'b1}}) n_no <= n_no + CNT_W'(1);
end else if (!is_next) begin
// Out of order: a die cannot rest on a level that is empty.
e_ord <= 1'b1;
if (n_no != {CNT_W{1'b1}}) n_no <= n_no + CNT_W'(1);
end else if (!known_good) begin
// §9: test precedes placement, because placement cannot be
// undone. An untested die is refused.
e_kgd <= 1'b1;
if (n_no != {CNT_W{1'b1}}) n_no <= n_no + CNT_W'(1);
end else begin
placed <= placed + LVLCNT_W'(1);
acc <= 1'b1;
if (n_ok != {CNT_W{1'b1}}) n_ok <= n_ok + CNT_W'(1);
end
end
end
end
assign levels_placed = placed;
// The frontier: the only level a die may join at.
assign next_level = complete_now ? LVL_IDX_W'(LEVELS - 1)
: LVL_IDX_W'(placed);
assign stack_complete = complete_now;
assign place_accepted = acc;
assign cnt_accepted = n_ok;
assign cnt_rejected = n_no;
assign err_out_of_order = e_ord;
assign err_not_known_good = e_kgd;
assign err_already_placed = e_dup;
assign err_place_after_complete = e_aft;
assign any_violation = e_ord | e_kgd | e_dup | e_aft;
endmoduleAll four refusals leave levels_placed untouched. §9 establishes that assembly cannot be undone, so a rejected placement must change nothing — a machine that advanced the frontier on a bad placement would model a stack containing a die that was never validly placed.
And the four reasons are separate outputs. Out-of-order is a sequencing bug, an untested die is a process-control failure, a re-placement is an impossible request, and placing onto a complete stack is a height error. Four different investigations, and one flag would collapse them into one.
13. What the Assertions Prove
// Bind unit note: P1-P5 reference tsv_count_budget's internals,
// P6-P10 tsv_repair_map's, P11-P15 stack_assembly_guard's.
// P1 -- §1: the interface count always includes the overhead.
property p_interface_includes_overhead;
@(posedge clk) disable iff (!rst_n)
(conductors_per_interface >= CNT_W'(SIGNALS));
endproperty
assert property (p_interface_includes_overhead);
// P2 -- §2's identity: bussed is symmetric, dedicated is N to 1.
property p_asymmetry_matches_the_model;
@(posedge clk) disable iff (!rst_n)
(is_bussed ? (asymmetry_num == asymmetry_den)
: (asymmetry_num == asymmetry_den * CNT_W'(CORE_DIES)));
endproperty
assert property (p_asymmetry_matches_the_model);
// P3 -- §2: a bussed organisation's per-die count does not depend on
// which die is queried. The property that makes "every core die is
// identical" checkable rather than asserted.
property p_bussed_count_is_uniform;
@(posedge clk) disable iff (!rst_n)
((is_bussed && q_in_range) |-> (q_conductors == conductors_per_interface));
endproperty
assert property (p_bussed_count_is_uniform);
// P4 -- §3: reaching die d traverses d+1 bodies, so the traversal
// count is strictly increasing with height and never zero.
property p_traversals_increase_with_level;
@(posedge clk) disable iff (!rst_n)
(q_in_range |-> ((q_traversals != '0) &&
(q_traversals == DIECNT_W'({{(32-DIE_IDX_W){1'b0}}, q_die} + 32'd1))));
endproperty
assert property (p_traversals_increase_with_level);
// P5 -- §3: the total is N(N+1)/2.
property p_total_traversals_is_triangular;
@(posedge clk) disable iff (!rst_n)
(total_traversals == CNT_W'((CORE_DIES * (CORE_DIES + 1)) / 2));
endproperty
assert property (p_total_traversals_is_triangular);
// P6 -- FORBIDDEN. A group never uses more spares than it has. §6:
// exhaustion is the boundary the whole grouping argument rests on.
property p_never_exceed_group_spares;
@(posedge clk) disable iff (!rst_n)
(spares_used[fail_group] <= SPRCNT_W'(SPARES_PER_GRP));
endproperty
assert property (p_never_exceed_group_spares);
// P7 -- FORBIDDEN. A member is never repaired twice. A second spare
// for one member consumes a spare another member may need.
property p_no_double_repair;
@(posedge clk) disable iff (!rst_n)
(err_double_repair |-> (total_spares_used == $past(total_spares_used, 1)));
endproperty
assert property (p_no_double_repair);
// P8 -- repaired and unrepairable are mutually exclusive, and
// neither holds without a report.
property p_verdict_is_exclusive;
@(posedge clk) disable iff (!rst_n)
(!(repaired && unrepairable) &&
((repaired || unrepairable) |-> $past(fail_valid, 1)));
endproperty
assert property (p_verdict_is_exclusive);
// P9 -- §6: unrepairable is NOT an error. The logic did nothing
// wrong; the defects landed badly. Conflating them would make a
// distribution problem look like a design fault.
property p_unrepairable_is_not_an_error;
@(posedge clk) disable iff (!rst_n)
(unrepairable |-> !any_error);
endproperty
assert property (p_unrepairable_is_not_an_error);
// P10 -- §6: the two tolerance bounds bracket the total, and the
// worst case is the per-group spare count. Published together
// because the count alone does not say which applies.
property p_tolerance_bounds_bracket;
@(posedge clk) disable iff (!rst_n)
((tolerable_best_case == total_spares) &&
(tolerable_worst_case == SPRCNT_W'(SPARES_PER_GRP)));
endproperty
assert property (p_tolerance_bounds_bracket);
// P11 -- FORBIDDEN. A die never joins out of order. §9: a die cannot
// rest on a level that is empty.
property p_no_out_of_order_placement;
@(posedge clk) disable iff (!rst_n)
(place_valid && !stack_complete &&
({{(32-LVL_IDX_W){1'b0}}, place_level} > {{(32-LVLCNT_W){1'b0}}, levels_placed}))
|=> (err_out_of_order && (levels_placed == $past(levels_placed, 1)));
endproperty
assert property (p_no_out_of_order_placement);
// P12 -- FORBIDDEN. An untested die never joins. §9: test precedes
// placement because placement cannot be undone.
property p_no_untested_placement;
@(posedge clk) disable iff (!rst_n)
(place_valid && !known_good) |=> !place_accepted;
endproperty
assert property (p_no_untested_placement);
// P13 -- FORBIDDEN. A placed level is never re-placed.
property p_no_replacement;
@(posedge clk) disable iff (!rst_n)
(place_valid &&
({{(32-LVL_IDX_W){1'b0}}, place_level} < {{(32-LVLCNT_W){1'b0}}, levels_placed}))
|=> (err_already_placed && (levels_placed == $past(levels_placed, 1)));
endproperty
assert property (p_no_replacement);
// P14 -- the frontier advances by exactly one per acceptance, and
// never otherwise. Rules out a machine that skips or repeats a level.
property p_frontier_advances_by_one;
@(posedge clk) disable iff (!rst_n)
(place_accepted ? (levels_placed == $past(levels_placed, 1) + LVLCNT_W'(1))
: (levels_placed == $past(levels_placed, 1)));
endproperty
assert property (p_frontier_advances_by_one);
// P15 -- every placement attempt is accounted exactly once.
property p_attempts_are_accounted;
@(posedge clk) disable iff (!rst_n)
(place_valid |=> ((cnt_accepted + cnt_rejected) ==
($past(cnt_accepted,1) + $past(cnt_rejected,1) + CNT_W'(1))));
endproperty
assert property (p_attempts_are_accounted);
// ── Cover.
// Both organisation models exercised, and both extremes of the stack.
cover property (@(posedge clk) disable iff (!rst_n) is_bussed && q_in_range);
cover property (@(posedge clk) disable iff (!rst_n) !is_bussed && q_in_range);
cover property (@(posedge clk) disable iff (!rst_n)
q_in_range && (q_die == '0));
cover property (@(posedge clk) disable iff (!rst_n)
q_in_range && (q_die == DIE_IDX_W'(CORE_DIES - 1)));
cover property (@(posedge clk) disable iff (!rst_n) err_die_out_of_range);
// Repair: a success, an exhaustion, and a double attempt.
cover property (@(posedge clk) disable iff (!rst_n) repaired);
cover property (@(posedge clk) disable iff (!rst_n) unrepairable);
cover property (@(posedge clk) disable iff (!rst_n) err_double_repair);
// §6's two distributions: spares spread one per group, versus two
// failures in one group. The same count, opposite outcomes.
cover property (@(posedge clk) disable iff (!rst_n)
(total_spares_used > TOTCNT_W'(1)) && (groups_exhausted == '0));
cover property (@(posedge clk) disable iff (!rst_n) groups_exhausted != '0);
cover property (@(posedge clk) disable iff (!rst_n) !stack_repairable);
// Assembly: EACH forbidden placement ATTEMPTED, plus a complete stack.
cover property (@(posedge clk) disable iff (!rst_n) err_out_of_order);
cover property (@(posedge clk) disable iff (!rst_n) err_not_known_good);
cover property (@(posedge clk) disable iff (!rst_n) err_already_placed);
cover property (@(posedge clk) disable iff (!rst_n) err_place_after_complete);
cover property (@(posedge clk) disable iff (!rst_n) stack_complete);14. Corner Cases
| Case | Behaviour | Why |
|---|---|---|
| Stack height of 6 | $fatal at elaboration | Outside 4.8 §2's verified set |
| Bussed model queried at any die | Identical count | P3 — every core die is the same die |
| Dedicated model queried at the bottom | N × the top die's count | §2, P2 — the asymmetry argument |
DIECNT_W too narrow | $fatal at elaboration | The deepest traversal would be understated |
| Zero spares per group | $fatal at elaboration | §5 — the first spare does almost all the work |
| Spares ≥ group members | $fatal at elaboration | A group that is all spare carries nothing |
| A single group | Index widths stay legal | A bare $clog2(1) would give an illegal [-1:0] |
| One failure in a group with one spare | repaired | The intended case |
| Two failures in one group with one spare | unrepairable, not an error | §6 — where defects landed, not a logic fault |
| The same member reported failed twice | err_double_repair; no spare consumed | P7 — a second spare denies another member |
| 48 failures, one per group | All repaired; groups_exhausted zero | §6's best case |
| 2 failures in one group, 46 spares unused | stack_repairable low | §6's worst case — same count, opposite outcome |
| Die placed at level 3 when 1 is the frontier | err_out_of_order; frontier unchanged | P11 — nothing to rest on |
| Untested die at the correct level | err_not_known_good; refused | P12 — placement cannot be undone |
| Die placed at an already-filled level | err_already_placed | P13 — the dies above are committed |
| Placement onto a complete stack | err_place_after_complete | Would exceed the verified height |
| Any refusal | levels_placed unchanged | P14 — a rejected placement changes nothing |
Rows ten and eleven are the pair this chapter exists to distinguish. Forty-eight failures can be entirely repairable and two can be fatal, with the same spares in both cases — and no count of spares or failures predicts which, only their distribution.
15. DV
// Independent references. The geometry reference counts by walking
// rather than multiplying; the repair reference models groups as
// explicit queues rather than as counters. SIMULATION-ONLY.
//
// NOTE: the probability arithmetic of §5 and §6 is deliberately NOT
// here. It is prose, computed outside the design, because no part of
// this module -- synthesisable or not -- should appear to predict a
// yield. The checks below verify COUNTING and BOOKKEEPING only.
class tsv_reference;
int signals, pg_num, pg_den, dies;
bit dedicated;
function void build(int s, int pn, int pd, int n, bit ded);
signals = s; pg_num = pn; pg_den = pd; dies = n; dedicated = ded;
endfunction
function int iface();
return (signals * (pg_den + pg_num)) / pg_den;
endfunction
// Walk the levels rather than multiplying.
function int conductors_at(int level);
int n = 0;
if (!dedicated) return iface();
for (int d = level; d < dies; d++) n += iface();
return n;
endfunction
// Walk the traversals rather than using the triangular formula.
function int total_traversals();
int n = 0;
for (int d = 1; d <= dies; d++) n += d;
return n;
endfunction
endclass
class repair_reference;
int per_group_spares;
int used[$]; // used[g] = spares consumed in group g
int claimed[$][$]; // claimed[g] = members already repaired
function void build(int groups, int spares);
per_group_spares = spares;
used.delete(); claimed.delete();
for (int g = 0; g < groups; g++) begin
used.push_back(0);
claimed.push_back({});
end
endfunction
// Returns 1 repaired, 0 unrepairable, -1 double-report.
function int report_fail(int g, int member);
foreach (claimed[g][i]) if (claimed[g][i] == member) return -1;
if (used[g] >= per_group_spares) return 0;
used[g]++;
claimed[g].push_back(member);
return 1;
endfunction
function int groups_exhausted();
int n = 0;
foreach (used[g]) if (used[g] >= per_group_spares) n++;
return n;
endfunction
endclass| Check | What it establishes |
|---|---|
Reference iface() against the DUT at 1:0, 1:1 and 1:2 | §1's overhead arithmetic |
Reference conductors_at() at every level, both models | P2, P3 by walking rather than multiplying |
Reference total_traversals() at heights 2, 4, 8, 12 | P5's triangular identity, independently summed |
| Parameterise height 6 | Elaboration fails rather than interpolating |
| Query the bottom and top die under both models | The four organisation covers |
| One failure per group across all 48 groups | All repaired; groups_exhausted zero |
| Two failures in one group | Second is unrepairable; any_error stays low |
Confirm unrepairable never raises any_error | P9 — the classification that matters |
| Report the same member twice | err_double_repair; spare count frozen |
| Exhaust every group, then report one more | stack_repairable low throughout |
Compare DUT and reference groups_exhausted over a random stream | Two representations of one map |
Place levels 0…N in order with known_good high | stack_complete; P14 throughout |
| Place level 2 when the frontier is 1 | P11; frontier unchanged |
Place with known_good low at the correct level | P12 |
| Re-place level 0 after the stack is built | P13 |
| Place once more after completion | P15's accounting and err_place_after_complete |
The seventh and tenth checks together produce the report worth publishing, because both are cases where the design is behaving correctly and the stack is not:
THE SAME SPARES, TWO OPPOSITE OUTCOMES
48 groups of 64, one spare each. 48 spares in total.
(A) 48 failures, one per group
total_spares_used 48
groups_exhausted 0
stack_repairable 1 <-- every failure repaired
any_error 0
unrepairable never asserted
(B) 2 failures, both in group 17
total_spares_used 1
groups_exhausted 1
stack_repairable 0 <-- stack is scrap
any_error 0 <-- NOTHING IS WRONG with the logic
unrepairable asserted once
every property PASSES in both runs.
diagnosis : 48 failures are fine and 2 are fatal, with identical
redundancy. The repairable count is not a property of the
spares -- it is a property of where the defects landed, which
§6 establishes and which no amount of spare counting reveals.
what makes it dangerous : "we have 48 spares" is the number a
design review hears, and it is true in both runs. The useful
number is the WORST CASE -- tolerable_worst_case, which is 1
per group regardless of the total -- and a review that hears
only the total will conclude the stack tolerates 48 failures
when it may tolerate 2.
and note any_error is LOW in run (B). The logic is correct; the
stack is unusable. A monitor that treated unrepairable as an
error would blame the wrong thing, which is why P9 forbids it.
the fix : publish both bounds (§12) and treat the worst case as
the design figure. §6's clustering caveat then matters
directly: clustered defects push the outcome toward run (B),
and real defects cluster.16. Debugging
| Symptom | Likely cause | How to confirm |
|---|---|---|
| Bottom die's via count far above the top's | The dedicated organisation — §2 | asymmetry_num over asymmetry_den; P2 |
| A different bottom die needed per stack height | The same cause — §2's argument against it | Bussing keeps every core die identical |
| Deepest traversal count looks one short | DIECNT_W too narrow — COUNT vs INDEX | The elaboration guard; P4 |
| Stack scrapped despite spares remaining | Failures clustered in one group — §6 | groups_exhausted against total_spares_used |
| “We have 48 spares” but 2 failures killed it | The total is the best case, not the design figure — §15 | tolerable_worst_case; it is per group |
| Spare consumed with no failure repaired | The same member reported twice | err_double_repair; P7 |
unrepairable treated as a logic fault | It is not one — §6, P9 | any_error is low; check the defect distribution |
| A defect low in the stack broke everything above | Expected — §3's serial dependency | Not a fault; identical defects have unequal consequences |
| Assembly advanced past a rejected placement | The frontier moved on a refusal | P14; all four refusals must leave it unchanged |
| A die joined untested | known_good driven high without a test | P12 catches the input, not the test |
| Vertical connections untestable before placement | Inherent — §9's tension | Nothing in this chapter resolves it |
Row five is the entry this chapter exists to add. A spare total is a best case and a per-group spare count is the design figure, and a review that hears only the total will overestimate what the stack tolerates by up to the number of groups.
Row seven is the classification that matters. An unrepairable stack is not a logic fault — the map did its job and the defects landed badly.
17. Misconceptions
“Vertical connections are scarce.” Chapter 26.2 §5 — geometrically they are abundant, using about 2% of a die's face. What is scarce is the probability that all of them work.
“A stack's via count grows with height.” §2. Under a bussed organisation it is constant. Only the dedicated model grows, and that model makes the bottom die a different die at every height.
“Every die in a stack is wired the same.” §2. Under bussing yes — which is the structural argument for it. Under the dedicated model the bottom die carries N times the top.
“A defect is a defect.” §3. A broken via low in the stack breaks connectivity to every die above it; the same defect at the top breaks one. Identical defects, unequal consequences.
“Per-via reliability of 0.9999 is excellent.” §5. A 12-high stack with 36,864 vias then has a 2.5% chance of working. The figure that matters is y^(T×N).
“Redundancy in a stack is an optimisation.” §5's callout. It is a precondition — without it the arithmetic makes deep stacks unmanufacturable at any plausible per-via reliability.
“More spares is proportionally better.” §6. The first spare removes 99.7% of the risk; every subsequent one adds far less. Redundancy is a steep, early, cheap win.
“A spare can replace any failed via.” §7. It replaces one in its own group. A global spare would need a crossbar over thousands of signals, costing more than it saves.
“Forty-eight spares tolerate forty-eight failures.” §7, §15. Between 2 and 48, depending entirely on distribution. The total is the best case.
“An unrepairable stack means a bug.” §7, P9. The logic is correct and the defects landed badly. any_error stays low deliberately.
“Clustering does not matter if the total is large enough.” §7. Clustering defeats grouped redundancy specifically, and grouping is what makes redundancy affordable. Real defects cluster.
“A bad die can be swapped out during assembly.” §9. Assembly is bottom-up with no revisiting — the dies below a defect are committed, which is why known-good-die testing exists.
“Testing before placement solves the problem.” §9. It bounds the cost. The vertical connections that most need testing do not exist until placement, and placement is irreversible.
“This chapter computed TSV physics.” The opening callout. It computed none. No capacitance, resistance, thermal or yield figure appears in any block, and every count and reliability here is illustrative.
18. Interview Reasoning
What is a through-silicon via, architecturally? A conductor passing vertically through a die's body, so connection count scales with the die's area rather than its perimeter — which is the exponent change that makes a thousand-bit interface possible.
How many does a stack need? A few thousand per die for a 1024-bit interface with power and ground overhead. Under a bussed organisation that count is constant with height; under a dedicated one the bottom die carries N times the top.
Which organisation would you expect, and why? Bussed. A dedicated organisation makes the bottom die depend on stack height, so a manufacturer would need a different bottom die per height — and stacking is valuable precisely because it combines ordinary, identical dies.
Why is a defect's position significant? A via reaching die k passes through every die below it, so a break low in the stack severs connectivity to everything above. Identical defects have unequal consequences, which no planar interface exhibits.
Why does a stack need spare connections when a planar interface does not? Because all of them must work and there are tens of thousands. At a per-via reliability of one failure in ten thousand, a 12-high stack has about a 2.5% chance of being fully functional.
How many spares are needed? Very few. One spare per group of 64 removes roughly 99.7% of the failure probability — the first spare is worth more than all subsequent ones combined.
Why group the spares rather than pooling them? A global spare would need a routing path to every position — a crossbar over thousands of signals, costing more than the redundancy saves. A group of 64 needs 64 paths.
What does grouping cost? Failures are only tolerable where the spares are. Forty-eight spares in 48 groups tolerate 48 failures if spread and 2 if two land in one group — so the total is a best case and the per-group count is the design figure.
Does defect clustering matter? A great deal, and specifically against grouped redundancy. Independent failures spread across groups; clustered ones concentrate and exhaust a single group while others go unused.
Why is known-good-die testing necessary? Because assembly is bottom-up and irreversible. A defect found after placement cannot be fixed by swapping the die, since the dies below are committed — so the only way to bound the cost is to prove each die good before it joins.
And what does that not solve? The connections that most need testing — the vertical ones — do not exist until the die is placed, and placement cannot be undone. That tension is inherent.
19. Exercises
-
Derive the total conductor count for a whole stack under both of §2's models at each verified height, and identify the height at which the dedicated model's total first exceeds twice the bussed model's.
-
§3 gives
N(N+1)/2traversals to reach every die. Derive the average number of die bodies a randomly chosen signal traverses, and say what it implies about where testing effort should concentrate. -
Using §5's model, find the per-via reliability at which a 12-high stack reaches a 99% chance of working with no repair. Comment on whether the figure is plausible and what that implies.
-
§6 shows the first spare removes 99.7% of the risk. Derive the general expression for the marginal benefit of the
(S+1)-th spare, and prove it is strictly decreasing. -
For 48 groups of 64 with one spare each, compute the probability that a given number of independent failures is fully repairable, for 2, 5 and 10 failures. Then repeat assuming all failures land in one group, and state which assumption a design review should use.
-
§7 argues a global spare pool would cost more than it saves. Estimate the crossing count a full crossbar over 3,072 conductors would need, and compare it against the 48 spares grouping provides.
-
Remove §12's guard that
SPARES_PER_GRPis at least 1, and set it to zero. Which properties fire, and which do not? Explain why P10 still passes. -
§9 identifies a tension between testing and irreversible placement. Propose the minimum additional capability that would resolve it, and say which layer of the stack would have to provide it.
20. Where This Goes
The vertical connection is now accounted for. A 1024-bit interface needs a few thousand conductors per die; a bussed organisation keeps that count constant with height and every core die identical, while a dedicated one makes the bottom die carry N times the top; a path to die k traverses k die bodies, so defects low in the stack cost more than defects high in it; and all of them must work.
Four results carry forward. Excellent per-connection reliability is not a meaningful standard when tens of thousands must all work — y^(T×N) collapses, and at one failure in ten thousand a 12-high stack works 2.5% of the time. Redundancy is a precondition rather than an optimisation, and it is remarkably cheap: one spare per 64 removes 99.7% of the risk, with steeply diminishing returns after. A spare total is a best case — 48 spares tolerate between 2 and 48 failures, decided by distribution rather than count, which makes the per-group figure the one a design review should use. And assembly is irreversible, which forces known-good-die testing and leaves a tension that testing cannot resolve.
Three things are left open, and two of them are this module's largest gaps.
Every via count, group size, spare count and reliability here is illustrative. The geometry and the probability arguments are general; the numbers are examples, because JEDEC refused every retrieval and both vendor briefs yielded no usable text.
Which of §2's two organisation models is used was not established. The structural argument favours bussing decisively and it is reasoning, not evidence.
And the initialisation and test infrastructure remains undelivered. Chapter 4.8's scope callout assigns it to this module, §8's callout explains why it could not be delivered, and §14's block models the shape a repair map must have rather than any device's mechanism. Together with 26.2 §9's undelivered base-die contents, these are two whole deferred topics this module names as open rather than filling with plausible architecture.
What all three chapters have established is what HBM is: a thousand-bit interface divided into sixteen semi-independent streams, reached through a face rather than an edge, stacked on ordinary dies whose connections need spares to work at all. None of them has asked what a system does with it.
Chapter 26.4 closes the module with that. Chapter 4.8 §2 warns that HBM's headline bandwidth “requires traffic that keeps all eight channels busy” and that “one channel's worth of traffic gets one channel's worth of bandwidth” — and 26.1 §4 doubled the relevant count to sixteen. The next chapter is about what an accelerator's architecture has to look like to supply sixteen independent streams, why height buys capacity and only more stacks buy bandwidth, and what happens to a design that gets that distinction the wrong way round.
Continue learning
Related tutorials
- Related topic
HBM Overview
HBM reaches hundreds of GB/s with a per-pin rate lower than DDR5's. It wins on width, not speed — and getting that width required changing the packaging, which adds a fourth design layer to array physics, device architecture and the interface.
- Related topic
Why HBM Exists
Connections scale with perimeter while capability scales with area, and the ratio falls as 4/L. The width that answers it divides into sixteen streams that preserve the 64-byte granule exactly.
- Related topic
2.5D Packaging for HBM
A bump pitch turns an area into a count, and the face-versus-edge advantage has the closed form L/4p. The surprise is that a full 1024-bit bump field uses about 2% of a die's face.
- Related topic
HBM for AI Accelerators
Stacks buy bandwidth and dies buy capacity. The same 96 GB bought as twelve short stacks delivers three times the bandwidth of four tall ones — and one requester cannot use any of it.
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.
