4.1.10 · D3Computer Architecture (Deep)

Worked examples — Cache lines, tags, index, offset

2,759 words13 min readBack to topic

This page is the worked-examples companion to the parent note on cache lines, tags, index, offset. There we derived the three-field split; here we exercise it against every case class the topic can hand you — every associativity, the degenerate ends (direct-mapped and fully associative), a zero/edge address, a word problem, and an exam twist. Nothing new is assumed: if a symbol shows up, it was earned in the parent note or is re-earned here.

Throughout this page, denotes the memory address we are splitting — a plain non-negative integer, written in hex (like 0x1234) or decimal. Every example takes some , cuts its bits, and reads off the three fields.

Figure s01 (below) draws that address row once, with the two scissor cuts labelled: cut 1 strips the low offset bits (accent red), cut 2 separates tag from index. Notice the caption "compute low-to-high": you always find the offset first, then the index, and the tag is whatever remains on the left.

Figure — Cache lines, tags, index, offset

The scenario matrix

Before working anything, let us list every kind of situation this topic can throw. If an example doesn't fit a cell here, we've missed a case. (Recall = the address being split.)

Cell What varies Why it's a distinct case
A. Direct-mapped , so Maximum index bits, minimum tag bits
B. -way set-assoc Bits shift from index to tag
C. Fully associative Zero index bits (degenerate low end)
D. Offset-boundary trap index does not start on a hex nibble Sign of a careless bit-peel
E. Zero / tiny address address , or All fields zero — the degenerate address
F. Line-size sweep change only Offset moves, index/tag react
G. Address-width sweep change only (e.g. 64-bit) Only tag changes
H. Word problem real hit/miss on a loop Locality meets the split
I. Exam twist given tag bits, solve backwards for Inverting the formula

The eight worked examples below cover cells A–I (one example hits two cells). We reuse the notation from the parent: = address bits, = bytes per line, = total lines, = ways, = sets, and = the address being split.

Recall The three formulas we keep reusing

Offset bits ::: Index bits ::: where Tag bits :::


Example 1 — Cell A: direct-mapped, clean split

Forecast: before reading on, guess — how many offset bits does a 32-byte line need? And since it's direct-mapped, is the index big or small?

  1. Offset bits . Why this step? Inside a 32-byte line there are 32 nameable bytes; it takes exactly 5 bits to count .
  2. Sets , so index bits . Why this step? Direct-mapped means one line per set, so there are as many rows as lines.
  3. Tag bits . Why this step? Every address bit not spent on picking a row or a byte must be stored and compared to confirm identity.
  4. Split 0x1234. In binary, 0x1234 = 0001 0010 0011 0100.
    • Offset = low 5 bits = 10100 = .
    • Index = next 7 bits = bits 11..5 = 0010001 wait — peel carefully: bits (high→low) are 0001 0010 0011 0100; strip low 5 (10100), left with 0001 0010 001; low 7 of that = 0010001 = .
    • Tag = top 4 bits = 0001 = .

Verify: reassemble . ✓ Bits add: . ✓


Example 2 — Cell B & I: 4-way, then invert to find

Forecast: in part (b), do we need more ways or fewer ways to make the tag bigger?

Part (a):

  1. Offset . Why? line size only.
  2. , index . Why? 8 lines share each set, so fewer rows than lines.
  3. Tag . Why? leftover.

Part (b) — invert:

  1. We want tag , so index bits. Why this step? The address is a fixed budget of 32 bits; fixing tag and offset pins the index.
  2. Index bits ⇒ sets. Why? index bits and set count are tied by .
  3. -way. Why? sets and ways always multiply to total lines.

Verify: part (a) bits ✓. Part (b): with , , index , tag ✓ — and bigger tag came from more ways (fewer sets), matching the parent's "tag grows by " rule: from part (a)'s 8-way (tag 19) to 64-way, went , so tag . ✓


Example 3 — Cell C: fully associative (zero index bits)

Forecast: with everything in one set, which field disappears?

  1. Fully associative ⇒ , so index bits. Why this step? One giant set means there is no "which row?" question — any line may live anywhere.
  2. Offset . Why? still one line's worth of bytes to name.
  3. Tag . Why? every non-offset bit is now identity, because the hardware must compare against every slot's tag.
  4. Bit-level split. Write the low 16 bits of : 0x1F80 = 0001 1111 1000 0000. There are 32 bits total; the 16 higher bits are all 0. Now cut off the low 6 as offset and take everything above as the 26-bit tag:
    • Offset = low 6 bits = 00 0000 = .
    • Index = 0 bits (there is no index field at all).
    • Tag = the 26 bits above bit 5. From 0001 1111 1000 0000, dropping the low 6 leaves 0001 1111 10, and padding the 16 unwritten high zeros gives the 26-bit value 00000000000000 0001111110 = . Equivalently the tag is the block number .

Figure s02 caption: (this figure serves Example 4; the bit walkthrough above is the s03-style split for Example 3 done in text). The key point to see here: the two scissor cuts of s01 collapse to one cut (offset only) because the index field has width zero — the whole left part is tag.

Verify: reassemble tag + offset ✓. Note the offset is 0 because 0x1F80 is a multiple of 64 (line-aligned). See Cache associativity & replacement policies for why fully associative needs a replacement policy but no index.


Example 4 — Cell D: the offset-boundary trap

Forecast: the index looks like it should be the 3rd-and-4th hex nibbles 0xBE. Bet against yourself: will it be?

Figure s02 (below) draws the low 16 bits of 0xDEADBEEF as a row of bit-cells. The low 6 (accent red) are the offset; the next 8 (black bracket) are the index. Watch how the index bracket starts in the middle of a hex nibble, not on a nibble boundary — that is the whole trap, made visible.

Figure — Cache lines, tags, index, offset
  1. Offset , index . So the low 14 bits are offset+index. Why this step? We must strip offset first; the index is bits 13..6, not bits 15..8.
  2. 0xDEADBEEF low 16 bits = BEEF = 1011 1110 1110 1111. Low 14 bits = 11 1110 1110 1111. Why? 14 = 8 index + 6 offset.
  3. Offset = low 6 = 101111 = 0x2F = byte . Why? lowest bits.
  4. Index = next 8 bits (bits 13..6) = 11111011 = 0xFB = set . Why? 6 is not a multiple of 4, so the index straddles a hex boundary — the naive nibble read 10111011 is wrong.

Verify: naive-nibble answer 0xBE? No — bits 13..6 of 0xBEEF are 11111011 = 0xFB, confirming the trap. Numeric: ✓.


Example 5 — Cell E: zero and sub-line addresses (degenerate)

Forecast: which of these three lands in set 0? Which lands in set 1?

  1. Offset , index , tag . Why? standard split, computed once for all three.
  2. (i) : every bit is zero ⇒ tag 0, index 0, offset 0. Why? the smallest address is the origin of line 0, byte 0.
  3. (ii) : 0000 0000 0000 1111. Offset = 1111 = ; index = ; tag = . Why? , so it's still inside line 0 — only the offset moved to the last byte.
  4. (iii) : 0000 0000 0001 0000. Offset = 0000 = ; index = 000001 = ; tag = . Why? is the first byte of the next line, so we roll into set 1, offset 0.

Verify: block numbers , , ; sets ✓. Case (ii) shows the degenerate "sub-line" address never leaves set 0. This is exactly spatial locality at work — bytes 0–15 travel together.


Example 6 — Cell F: line-size sweep (offset moves, index reacts)

Forecast: bigger lines — does the index shrink or grow?

Figure s03 (below) stacks the two address splits ( on top, below) as bit-width bars. The accent-red offset bar grows by 3 as we go to the fatter line, while the index bar shrinks by exactly 3 — the arrow shows that trade. The tag bar is the same width in both rows: that is the punchline.

Figure — Cache lines, tags, index, offset
  1. Number of lines . For : ; for : . Why this step? total data is fixed, so a fatter line means fewer lines.
  2. : offset ; direct-mapped ⇒ , index ; tag .
  3. : offset ; , index ; tag . Why? offset gained 3 bits, index lost 3 bits — they trade one-for-one when total bytes is fixed.
  4. Observe: tag stayed 18 in both. Why? , and total bytes didn't change: .

Verify: : ✓; : ✓; both give ✓.


Example 7 — Cell G: 64-bit address (only tag grows)

Forecast: the cache geometry is identical — so which single field absorbs all 48 extra bits?

  1. Offset unchanged. Why? offset depends only on .
  2. Index unchanged. Why? index depends only on , and didn't change.
  3. Tag . Why? all extra address bits pile into the tag.

Verify: ✓; compared to Example 1's 4-bit tag, the tag grew by exactly ✓. Real 64-bit machines often use fewer physical bits — see Virtual vs physical addressing (VIPT/PIPT) and TLB and page tables for why the tag is usually built from the physical address.


Example 8 — Cell H: word-problem, hits and misses on a loop

Forecast: how many of these 17 accesses actually go to DRAM?

  1. Offset . So a line covers 64 bytes = 16 floats. Why this step? we need the block boundary to see who shares a line.
  2. Line of 0x1000: block . Addresses 0x1000 through 0x103F all have block same line. Why? 0x103F = 4159, ; 0x1040 = 4160, — new line.
  3. So 0x1000 misses (cold — fetches the whole line), and the next 15 reads 0x10040x103C hit. Then 0x1040 misses (new line 65). Why? first touch of a line is a compulsory miss; the rest of that line rides along for free — that's spatial locality paying off.

Verify: 17 accesses = addresses 0x1000 (off 0) up to 0x1040 (off 0 of next line), stride 4. Line-64 accesses: offsets = 16 accesses, of which 1 miss + 15 hits. Then 0x1040 = 1 miss. Total misses , hits . Miss rate ✓. With a 100 ns miss vs ~1 ns hit (Memory hierarchy and latency numbers), the line fetch amortizes beautifully.


Recall check

Recall Rapid-fire (cover the answers)

Fully associative ⇒ how many index bits? ::: Zero, because and . If total bytes is fixed, do tag bits change when line size changes? ::: No — tag , independent of . Given tag , , , : what is ? ::: -way (index , , ). For 0xDEADBEEF, 6 offset + 8 index bits: which set? ::: (0xFB) — strip offset first, index is bits 13..6. Address 16 with lands in which set (direct-mapped)? ::: Set 1, offset 0 — first byte of the next line. Loop over 0x1000…0x1040 stride 4, , empty cache: hits and misses? ::: 2 misses, 15 hits.


Back to the parent: Cache lines, tags, index, offset (index 4.1.10). Related depth: Cache associativity & replacement policies, Cache coherence (MESI).