Anatomy of a cache: the four questions and the exam cache table

After this lesson you will place, find, evict and write back blocks in a cache of any organisation, split an address into tag, index and offset by hand — and fill the exam's cache table row by row, LRU order and dirty bits included.

Exercise 3 of the 15 July 2024 paper sets up "a 32-block main memory with a 4-way set associative 8-block cache based on a write allocate with write-back protocol with LRU". From a cold start the processor makes ten accesses: W16, W14, W07, R14, W16, R12, W18, R06, W04, W30. Seven of the ten are writes. The last column of the table to complete asks, row by row: Write in memory?

Guess first: how many rows say Yes? Seven, one per write? The correct table has only two. And the first of the two is a read: access 8, R06, writes block 14 into main memory — a block the program last wrote at access 2.

Why would a read write to memory, and where did the seven writes go? Each column of that table answers one of the four questions every cache must answer. By the end of the lesson you fill the table yourself.

What one line of the cache holds

Lesson 22 left the cache as a small, fast memory that keeps copies of memory blocks: a hit when the block is there, a miss when it must be fetched. Now open it. The paper's cache has eight lines, called a to h, in front of a main memory of 32 blocks numbered 0 to 31. Cache and memory exchange whole blocks, and the paper counts in blocks: its "memory addresses" 0 … 31 are block numbers, or block addresses.

Cache line (or cache block) — one row of the cache: a valid bit V, a dirty bit D, a tag, the data of one block and, in a set-associative cache, a few bits that record the order of use (the LRU bits).

V D Tag Data LRU 1 1 1001 block 18 4 line d set 0 set 1 a b c d e f g h 16 14 12 18 7 empty empty empty 2 1 3 4 1 way 0 way 1 way 2 way 3 block 18 = tag 1001, set 0
Fig. 1. The paper's cache after its seventh access (W18): two sets of four ways, lines a–h. Filled boxes are dirty lines (written, memory stale); the small number orders each set by last use, 1 = least recently used. Above, line d enlarged: valid, dirty, tag 1001, the words of block 18, and the most recent use of its set.

Read block 18 in five bits: 10010. Its last bit, 0, chose set 0; the four bits before it, 1001, are the tag the line stores. The tag is what tells block 18 from the fifteen other even blocks that could sit in set 0 — four bits for sixteen candidates.

Why the high-order bits for the tag and the low-order bits for the place? Because consecutive blocks — the next elements of an array, the next instructions of a loop — differ in their low bits. Taking the place from the low bits spreads them over consecutive sets, where they do not compete. Had the high bits chosen the place, a whole array would pile into one set and evict itself.

Every design decision of a cache answers one of four questions (notes, p. 304; Hennessy and Patterson ask the same four): where can a block be placed, how is it found, which block is replaced on a miss, and what happens on a write. Sections 2–5 take them in turn; section 6 puts them together in the exam table.

Placement: where may a block go?

Start with a loop that reads one array and writes another, in the mnemonics of the exam papers (registers without the dollar sign, as the 2026 paper writes them; older papers write $F2):

I0  LOOP: LD    F2, 0(R1)
I1        FMUL  F4, F2, F0
I2        SD    F4, 0(R2)
I3        ADDUI R1, R1, 8
I4        ADDUI R2, R2, 8
I5        BNE   R1, R3, LOOP

I0 reads X[i], I2 writes Y[i]. Take blocks of four doubles (32 bytes) and suppose that X[0 … 3] sit in memory block 12 and Y[0 … 3] in block 20. The first four iterations touch the blocks 12, 20, 12, 20, 12, 20, 12, 20 — the alternation of the notes' example on p. 306. Where may an eight-line cache put them?

Direct-mapped: every block has exactly one place, line (block address) mod 8. Here 12 mod 8 = 4 and 20 mod 8 = 4: both blocks want line 4. I0 brings block 12 in, I2 throws it out to make room for block 20 (with write-allocate, section 5), the next I0 throws block 20 out again — and since I2 wrote it, a write-back cache must first copy it to memory. Every access misses while seven lines stay idle.

2-way set-associative: the same eight lines, grouped in four sets of two. Now the rule picks a set, not a line: 12 mod 4 = 0 and 20 mod 4 = 0, so both go to set 0 — which has two places. Two misses fill it, and every later access hits.

0 1 2 3 4 5 6 7 12 / 20 line 12 mod 8 = 4 20 mod 8 = 4 R12W20R12W20 R12W20R12W20 MMMM MMMM 8 accesses, 8 misses

Before: direct-mapped, 8 sets of 1 line

0 1 2 3 12 20 way 0 way 1 12 mod 4 = 0 20 mod 4 = 0 R12W20R12W20 R12W20R12W20 MMHH HHHH 2 misses, then 6 hits

After: 2-way, 4 sets of 2 lines

Fig. 2. The same eight lines, two organisations, and the eight accesses of the loop's first four iterations underneath (M = miss, H = hit). Before: blocks 12 and 20 both map to line 4 and throw each other out at every access. After: both map to set 0, which has two ways; after two misses every access hits.

The two organisations are one rule with different numbers. Group the lines of the cache into sets of n lines, the ways; a block may go into any way of exactly one set:

\[ \#\text{blocks} = \frac{\text{cache size}}{\text{block size}} \] \[ \#\text{sets} = \frac{\#\text{blocks}}{n} \] \[ \text{set} = \text{block address} \bmod \#\text{sets} \] \[ \text{tag} = \text{block address} \;\operatorname{div}\; \#\text{sets} \]
\(n\)
ways per set: the associativity
\(\#\text{blocks}\)
lines of the cache (8 in the paper)
\(\operatorname{div}\)
integer division: the quotient, rounded down

Direct-mapped is the case n = 1: every set is one line, so the set number is the line number — the notes' equation (73), line = block address mod #blocks. Fully associative is the other end, n = #blocks: one single set, no set number at all, and the whole block address is the tag; a block may go anywhere. Everything in between is n-way set-associative, the notes' equation (74).

A block goes to exactly one set — block address mod #sets — and into any free way of that set.

What the freedom costs. To find a block, the cache compares the tags of all n ways of the set at once: n comparators as wide as the tag, plus a multiplexer that picks the data of the way that matched. A direct-mapped cache needs one comparator and can hand out the data while the tag is still being checked, which makes it the simplest and fastest. A fully associative cache needs one comparator per line: the most flexible, the most expensive, the slowest — kept for small structures. Set-associative is the compromise: few conflicts for a few comparators.

Identification: tag, index, offset

The paper hands you block numbers. A processor issues addresses of bytes (or words), and a block holds many of them. Take I0 of the loop in its third iteration: it reads X[2], at byte address 400 if X starts at 384 = 12 × 32. Then 400 = 12 × 32 + 16: block 12, byte 16 of the block. With 16-bit addresses and the 2-way cache of Fig. 2 (32-byte blocks, 4 sets), the bits of 400 split as 000000011 | 00 | 10000: the last five bits are the byte inside the block (16), the next two the set (12 mod 4 = 0), the remaining nine the tag (12 div 4 = 3).

The cache reads these three fields off every address, from right to left: the offset (which byte or word inside the block), the index (which set) and the tag (everything else). With powers of two, each field is a count of bits:

\[ \text{offset} = \log_2(\text{block size}) \] \[ \text{index} = \log_2(\#\text{sets}) \] \[ \text{tag} = A - \text{index} - \text{offset} \]
\(A\)
address bits: 32 for a memory of 4 G addressable units
block size
in the unit the address counts: bytes, or words in a word-addressed quiz
\(\#\text{sets}\)
cache size / (block size × n), as in the placement rule

In the paper's units the offset is gone — its addresses are already block numbers — and the split is 5 = 4 + 1: a 4-bit tag and a 1-bit index, the last bit, which says whether the block is even (set 0) or odd (set 1).

Finding a block is one parallel step. The index selects the set; the tag of every way in it is compared with the tag of the address, all at once; a way hits when its V is 1 and its tag is equal. The index itself is never compared with anything — a line does not even store it, because its position is the index. That is why only the tag needs storing.

way 0 way 1 V Tag Data V Tag Data 0 1 2 3 1 0101 words 1 1001 words = = no match hit data out tag 1001 index offset address index: selects, never compared
Fig. 3. Identification in a 2-way set-associative cache. The index of the address selects one set (here set 2) in both ways; two comparators — one per way — compare the stored tags with the address tag, each also checking V. Way 1 matches: hit, and its block goes out; the offset then picks the word inside it.

The July 2024 quiz, one step at a time

Quiz 2 of the same paper asks for exactly these widths: "main memory size = 4 Giga word; 4-way set-associative cache with cache size = 1 Mega word and block size = 256 word", addressed at word level, and four candidate structures of the 32-bit address.

  1. Address width. The memory holds \(4\text{ G} = 2^{32}\) words and the address counts words:

    \[ A = 32 . \]
  2. Offset. A block holds \(256 = 2^{8}\) words, so the word inside it takes

    \[ \text{offset} = \log_2 256 = 8 \text{ bits}. \]
  3. Lines of the cache: cache size over block size.

    \[ \#\text{blocks} = \frac{2^{20}}{2^{8}} = 2^{12} = 4096 . \]
  4. Sets: four ways share each set, and the index numbers the sets.

    \[ \#\text{sets} = \frac{4096}{4} = 2^{10} \] \[ \text{index} = \log_2 2^{10} = 10 \text{ bits}. \]
  5. Tag: what is left of the 32 bits.

    \[ \text{tag} = 32 - 10 - 8 = 14 \text{ bits}. \]

    The address reads |14-bit tag | 10-bit index | 8-bit offset| — Answer 3.

  6. The other answers are the same cache at other associativities: 12 | 12 | 8 is direct-mapped (4096 sets), 24 | — | 8 fully associative (one set, no index), 13 | 11 | 8 two-way. Doubling the ways halves the sets: one bit moves from the index to the tag, and the offset never moves.

A check that needs no set count: the tag is \(A - \log_2(\text{cache size} / n)\) bits — here \(32 - \log_2(2^{20}/4) = 14\). It does not depend on the block size at all: a larger block only moves bits from the index to the offset.

Address splitter and quiz generator

The bar is the address drawn to scale: the violet field is the tag ("is this block mine?"), the plain one the index ("which set?"), the grey one the offset ("which word inside the block?"). The sliders start at the July 2024 quiz. "Quiz me" asks new questions in the paper's form: work them out by hand, pick an answer, press "Check". The insight: offset = log₂ block size, index = log₂ #sets, tag = the rest.

  1. Before moving anything: what happens to the three fields if the ways go from 4 to 8?
    What you should see

    The sets halve, 1024 → 512, so the index loses a bit (10 → 9) and the tag gains it (14 → 15). The offset stays 8: it depends only on the block size.

  2. Push "ways per set" to the end. What is left of the index, and which answer of the paper's quiz is this?
    What you should see

    Nothing: with 4096 ways the whole cache is one set — fully associative, |24-bit tag | 8-bit offset|, Answer 2. The comparators readout says 4096 × 24-bit: that is why fully associative caches stay small.

  3. Back to 4 ways, then double the block size to 512 words. Which fields change?
    What you should see

    Offset 8 → 9 and index 10 → 9: half as many lines, twice as large. The tag stays 14 bits — the tip above in action.

  4. Switch the mode to "quiz me" and answer three questions, each by hand first.
    What you should see

    Three lines solve every instance: offset = log₂ of the block size; index = log₂ of cache size / (block size × ways); tag = the rest. The wrong options are the direct-mapped, the fully associative and the half- or double-associativity splits of the same cache.

The notes' Example 4 (p. 304) draws a line with tag 0x001AE, then asks whether the row for address 0x1AE023 holds 0x1AE. Do they agree?

Yes, once you fix the widths the example leaves out. Take a 32-bit address, 0x001AE023, with 256 sets (8 index bits) of 16-byte blocks (4 offset bits). A hex digit is four bits, so the split falls on digits: offset 0x3, index 0x02, tag = the top 20 bits = 0x001AE, the same number as 0x1AE. Without the widths no split can be checked: always state them.

The first description of a cache fills two pages: Maurice Wilkes, "Slave Memories and Dynamic Storage Allocation", IEEE Transactions on Electronic Computers, April 1965. Main memory: 64K words, 16 address bits; the "slave": 32 words, 5 address bits. The word at address r goes into slave register r (mod 32), and "the 11 most significant bits of r are copied into the 11 tag bits" — his r = 10 259 = 320 · 2⁵ + 19 lands in register 19 with tag 320. A write updates the word "in the slave as well as in the main memory". Direct-mapped, high-order tag, write-through: three answers of this lesson, in 1965.

Replacement: which block leaves?

Access 8 of the paper, R06, needs a line in set 0 (6 is even), and set 0 is full: block 16 came in at access 1 and was written again at access 5; block 14 came in at access 2 and was read at access 4; block 12 came at access 6, block 18 at access 7. One of the four must go. A direct-mapped cache never faces this choice — the block's only line is emptied, whatever it holds. With n ways there are n candidates, and a replacement policy picks one:

access 1234 5678 16 14 12 18 access 8: R06 misses first in, first out

FIFO: the oldest arrival goes — block 16

access 1234 5678 16 14 12 18 access 8: R06 misses unused the longest

LRU: the oldest use goes — block 14

Fig. 4. Set 0 of the July 2024 cache just before access 8. Squares: the access that brought each block in; rings: later uses. FIFO looks at the squares and evicts block 16, which arrived first; LRU looks at each block's last mark and evicts block 14, untouched since access 4. The paper says LRU: the victim is 14.

"LRU evicts the oldest block" is the trap: oldest by arrival is FIFO. Block 16 is older than 14, but it was used more recently. The notes' Example 7 (p. 310) is the two-way version: set 0 holds block 4, last used 5 cycles ago, and block 8, last used 20 cycles ago; block 12 misses into set 0 and LRU replaces block 8.

What LRU costs. A set of n ways can be in n! orders of use, so exact LRU needs \(\lceil \log_2 n! \rceil\) bits per set: 1 bit for 2 ways, 5 for 4 ways, 16 for 8 ways, rewritten on every hit. Beyond four ways designers settle for pseudo-LRU — a binary tree of n − 1 bits that points away from the half used most recently — or for random, which costs nothing and loses little in a large cache. (The bit counts are not in the notes, which give only the trend, p. 318: LRU for 2–4 ways, pseudo-LRU or random above.)

Why would anyone choose random?

Because LRU has a worst case that random does not. Cycle through n + 1 blocks that all map to one n-way set — three blocks A, B, C in a 2-way set: A, B, C, A, B, C … At every miss LRU evicts exactly the block that is needed next, so every access misses, forever. Random sometimes keeps it, and then hits. It is the kind of "pathological pattern" the notes mention on p. 309 without giving one.

What happens on a write

A read leaves cache and memory equal; a write makes them differ. Take a loop that keeps a running total in memory:

I0  LOOP: LD    F2, 0(R1)
I1        FADD  F4, F4, F2
I2        SD    F4, 0(R3)
I3        ADDUI R1, R1, 8
I4        BNE   R1, R2, LOOP

In eight iterations I2 stores eight times to the same word S. What does main memory see? Two separate decisions answer it: where a write goes, and what a write miss does.

Where the write goes

Write-through: the value is written into the cache line (if the block is there) and into main memory. Memory is always up to date and the policy is simple — but every store becomes a memory write, eight for the loop, each as slow as main memory. A write buffer hides that time: the store drops address and value into a small queue and the processor goes on while the buffer drains to memory; it stalls only when the buffer is full.

Write-back: the value is written into the cache only, and the line's dirty bit is set. Memory receives the block once, when the dirty block is evicted: the eight stores of the loop cost one block write. The price is a dirty bit per line, a memory that is stale until the eviction, and a miss whose victim is dirty must write it back before (or while) it fetches the new block — exactly the "read that writes" of the puzzle.

What a write miss does

The first I2 misses: S has never been in the cache. Write-allocate treats a write miss like a read miss: fetch the block into a line, then write it there — the next seven stores hit. No-write-allocate (write-around) sends the word straight to memory and leaves the cache alone — so the next store to S misses again. It suits data written once and not read soon, such as an output array: no fetch, no useful block evicted.

What one store costs in memory traffic, for the four pairs of policies (notes, pp. 313–314).
PoliciesWrite hitWrite miss
write-back + write-allocate (most common)nothing now; D ← 11 block read now, 1 block write when evicted
write-back + no-write-allocatenothing now; D ← 11 word write; cache unchanged
write-through + write-allocate1 word write1 block read + 1 word write
write-through + no-write-allocate (common)1 word write1 word write; cache unchanged

Why the policies pair as they do (the notes' Table 26). With write-back, allocation pays: once the block is in the cache, every later store to it is a hit that costs no traffic. With write-through, every store goes to memory anyway, so fetching the block on a write miss buys nothing but the fetch. Hence write-back with write-allocate, and write-through with no-write-allocate.

Write-policy traffic meter

Three loops, blocks of four words, a cache large enough to hold all their blocks; after the loop the blocks are evicted, so the deferred write-backs are counted. Step through the sixteen accesses: blue arrows are memory traffic (fetch, word write, write-back); a filled D cell is a dirty line; "stale" marks a memory block older than its cached copy. The table under the picture keeps the running bill of all four policy pairs; yours is highlighted. The insight: why the policies pair as they do, and what each costs.

  1. The meter starts with write-through + no-write-allocate on the running total. Predict the memory transactions after the whole loop, then press "Play".
    What you should see

    10: two block fetches for X (X[0] and X[4] miss) and eight word writes for S — every store goes around the cache, because S is never brought in.

  2. Switch to write-back + write-allocate and predict again.
    What you should see

    4: the two fetches of X, one fetch of S at the first store, one write-back of S after the loop. Eight stores, one memory write: from the second store on, they hit a dirty line.

  3. Keep write-back but choose no-write-allocate. Does write-back still save anything?
    What you should see

    No: 10 transactions again. S is never allocated, so every store misses and goes to memory word by word — a write-back cache can only collect the writes of blocks it holds. That is why write-back pairs with write-allocate.

  4. Choose the copy loop of section 2 and compare the two write-through pairs. Which is worse, and what are the fetched blocks of Y used for?
    What you should see

    Write-through + write-allocate: 12 transactions and 24 words, against 10 and 16. The two blocks of Y are fetched and never read — every store goes to memory anyway. With write-through, allocation is pure cost: that is why it pairs with no-write-allocate.

"A dirty line holds wrong data." The other way round: the dirty line holds the newest value, and memory holds the stale one. And "write-through bypasses the cache" is wrong too: on a write hit the line is updated as well — memory is written in addition, not instead.

With write-back, memory is written only when a dirty block leaves the cache — that is the "Write in memory" column of the exam.

The exam cache table, end to end

Back to Exercise 3. The paper first asks "How many sets?": 8 blocks / 4 ways = 2, Answer 1. The 5-bit block address then splits into a 4-bit tag and a 1-bit set: set = address mod 2 (is the address even or odd?), tag = address div 2. Lines a–d form set 0, lines e–h set 1 (the natural reading; the paper does not say). A missing block goes into the first free line of its set in alphabetical order — the paper's given row 1 puts block 16 into line a — and into the LRU line once the set is full. Each row then takes six moves:

  1. Set = address mod #sets; Cache Tag = address div #sets, in binary — four digits here.
  2. Look in the set. Same tag with V = 1: Hit, in that line. Otherwise a miss, and the first reference to a block is a Cold-start Miss — the paper's name for a compulsory miss (lesson 24 names the other kinds).
  3. Cache Address: the line that hit; on a miss, the first free line of the set, or the LRU line if the set is full.
  4. Dirty bit: 1 after a write (write-allocate fills the line, then writes it), 0 after a read fill; a read hit leaves it as it was.
  5. Write in memory: Yes only when the evicted block is dirty — never because of the write itself.
  6. Update the order of use of the set: the accessed block becomes the most recent, hits included.

The cache table, row by row

The table has the paper's columns plus a scratch column with the set after each access, least recently used first (with FIFO, oldest arrival first); row 1 is given, as in the paper, which leaves only its Cache Tag and Set for you. Choose the six cells of the next row and press "Check this row": a right row is entered, a wrong one shows its right cells green and its wrong cells red. Under the table, the cache after the last entered row: one row per set, one box per way, filled = dirty, small number = the same order (1 = the first to go once the set is full), struck-out number = the block just evicted. The last controls change the cache, the policies and the accesses; the table then starts again. The insight: set = address mod #sets, tag = address div #sets, and memory is written only when a dirty block is evicted.

  1. Row 2 is Write [14]. Before choosing: which set, which tag, which line — and is memory written?
    What you should see

    14 mod 2 = 0 → set 0; 14 div 2 = 7 → tag 0111; line a holds block 16, so the first free line of set 0 is b. A write fill: Dirty bit 1. Nothing is evicted: Write in memory No. A first reference: Cold-start Miss.

  2. Fill up to row 7, then predict row 8, Read [06]: which line, which dirty bit, and is memory written?
    What you should see

    Set 0 is full and the scratch column reads 14 16 12 18: block 14, in line b, is the least recently used. It is dirty since access 2, so it is written back — Write in memory Yes. Block 6 is only read, so the new line is clean: Dirty bit 0. Tag 6 div 2 = 3 = 0011.

  3. Set "ways per set" to 2-way and press "Fill the table". Same misses? Same write-backs? Then try direct-mapped and 8-way.
    What you should see

    Eight misses every time, all cold-start. The write-backs change: three with 2 ways (blocks 14, 16 and 18), one direct-mapped (block 14), none with 8 ways — a fully associative cache of 8 lines holds all eight blocks and never evicts. Watch the Set and Cache Tag columns follow the number of sets.

  4. Back to 4-way, switch to write-through + no-write-allocate and fill the table. How many rows say Yes, and why does row 4, Read [14], now miss?
    What you should see

    Seven: every write goes to memory. Row 4 misses because row 2 never brought block 14 in — with no-write-allocate a write miss leaves the cache alone — so all ten accesses miss.

The puzzle, solved. The seven writes never reached memory when they happened: write-back keeps them in the cache and sets the dirty bit. Memory is written only twice, at the two evictions of a dirty block. Access 8, a read of block 6, needs a line of the full set 0, and LRU picks block 14 — dirty since access 2, so the value written at access 2 reaches memory six accesses later, during a read. Access 9 evicts block 16, dirty since accesses 1 and 5. Access 10 evicts block 12, which was only ever read: clean, nothing to write. Eight misses in ten accesses, a miss rate of 80 %; and when the sequence ends, blocks 4, 30, 18 and 7 are still dirty in the cache — their writes reach memory only when they are evicted.

Exam-style questions

Explain the main concepts and benefits of each one of the following techniques used in cache memories: (a) direct-mapped block placement; (b) fully associative block placement; (c) set-associative block placement; (d) block identification by tag comparison; (e) LRU replacement; (f) write-back with dirty bit; (g) write-through with write buffer; (h) write allocate and no write allocate. (Five lines each.)

Show model answer

(a) Each memory block can be placed in exactly one cache line, line = block address mod #blocks: the index bits select the line and a single tag comparison decides hit or miss. Benefit: the simplest and fastest organisation — one comparator, no replacement decision, and the data can be read while the tag is being checked. Cost: conflict misses — two blocks with the same index evict each other at every access (blocks 12 and 20 in an 8-line cache), even while other lines are empty.

(b) A block may be placed in any line: there is no index, the whole block address is the tag, and the address tag is compared with the tags of all lines in parallel. Benefit: no conflict misses, and full freedom for the replacement policy. Cost: one comparator per line plus the logic that selects the matching line — expensive in area and energy, and slower, so it is used only for small caches and buffers.

(c) The cache is divided into sets of n lines (ways); a memory block maps to exactly one set, set = block address mod #sets, and may occupy any of its n ways. Benefit: far fewer conflict misses than direct-mapped (two blocks with the same index can coexist) at a much lower cost than fully associative, since only the n tags of one set are compared. Cost: n comparators and an n-way multiplexer, so a slightly longer hit time and more energy.

(d) The address is split into tag | index | offset: offset = log2(block size), index = log2(#sets), tag = the remaining high-order bits. The index selects the set, the n stored tags of that set are compared with the address tag in parallel, and a way hits if its tag is equal and its valid bit is 1; the offset then selects the word. Benefit: one parallel lookup; only the tag is stored, since index and offset are implied by the position; the valid bit prevents false hits at cold start.

(e) On a miss into a full set, the block whose last use (read or write, hit or fill) is oldest is evicted; the order of use is updated on every access. Benefit: it exploits temporal locality — recently used blocks are likely to be used again — so it usually misses less than FIFO or random. Cost: state per set grows as log2(n!) bits and must be updated on every hit, so highly associative caches use pseudo-LRU or random.

(f) A write updates only the cache line and sets its dirty bit; main memory is updated with the whole block only when a dirty block is evicted. Benefit: many writes to the same block cost one memory write, less memory traffic and writes at cache speed. Cost: a dirty bit per line, memory stale until eviction, and a miss that evicts a dirty block must write it back as well.

(g) Every write updates the cache (on a hit) and main memory; a small FIFO write buffer holds the pending writes so the processor continues while they drain to memory, stalling only if the buffer is full. Benefit: memory is always up to date, simple control, no write-back on eviction. Cost: one memory transaction per store, so high traffic; without the buffer every store would stall for a memory write.

(h) On a write miss, write allocate fetches the block into the cache and then writes it (as a read miss would); no write allocate writes the word directly to memory and leaves the cache unchanged. Write allocate is paired with write-back: once allocated, further writes to the block hit and generate no traffic. No write allocate is paired with write-through: every write goes to memory anyway, so fetching the block would only add traffic; it also keeps data that is written once and not read soon from evicting useful blocks.

Eight sub-points for 8 points, five lines each: the form of the June 2026 open question. Every answer needs three things: the mechanism in one sentence (with the formula where there is one: mod #blocks, mod #sets, log2), what it buys, what it costs. Do not write "LRU evicts the oldest block" in (e): that describes FIFO. In (h) give the pairing and its reason, not the pairing alone.

Let's consider the following memory hierarchy addressed at word-level (32-bit): main memory size = 4 Giga word; 4-way set-associative cache with cache size = 1 Mega word and block size = 256 word. Please indicate the structure of the 32-bit memory address: (SINGLE ANSWER)

  • Answer 1: |12-bit tag | 12-bit index | 8-bit offset| 12 index bits would mean 4096 sets: that is the same cache direct-mapped, one line per set.
  • Answer 2: |24-bit tag | 8-bit offset| No index at all means a single set: fully associative, not 4-way.
  • Answer 3: |14-bit tag | 10-bit index | 8-bit offset| Offset log2 256 = 8; 2^20 / 2^8 = 4096 lines; 4096 / 4 = 1024 sets → 10 index bits; 32 − 10 − 8 = 14.
  • Answer 4: |13-bit tag | 11-bit index | 8-bit offset| 11 index bits = 2048 sets of 2 ways: the 2-way version of the cache.
Show model answer

Answer 3. Offset = log2(256) = 8 bits; number of blocks = 2^20 / 2^8 = 2^12; number of sets = 2^12 / 4 = 2^10, so index = 10 bits; tag = 32 − 10 − 8 = 14 bits.

Quiz 2 of the 15 July 2024 paper, in its own words. The three distractors are the same cache at 1, 2 and 4096 ways — so if you only remember that "more ways move bits from the index to the tag", you can still pick the right one.

Let's consider a 32-block main memory with a 4-way set associative 8-block cache based on a write allocate with write-back protocol with LRU. The addresses are expressed as: Memory Addresses [0, 1, 2, … 31]10; Cache Addresses [a, b, c, d, e, f, g, h], and Cache Tags are expressed as binary numbers. How many sets? (Answer 1: 2; Answer 2: 4; Answer 3: 8; Answer 4: 16.) At cold start the cache is empty, then there is the following sequence of memory accesses: Write 16, Write 14, Write 07, Read 14, Write 16, Read 12, Write 18, Read 06, Write 04, Write 30. Please complete the table (Hit/Miss Type, Cache Tag, Set, Cache Address, Dirty bit, Write in memory). How much is the miss rate?

Show model answer

Sets = 8 / 4 = 2 (Answer 1). The 5-bit block address = 4-bit tag | 1-bit set: set = address mod 2, tag = address div 2. Set 0 = lines a–d, set 1 = lines e–h; a free line is taken in alphabetical order, otherwise the LRU line of the set. "Yes (14)" means: the evicted block 14 is dirty and is written back to memory. The last column is scratch work, not a column of the paper.

Type of memory accessMemory AddressHit/ Miss TypeCache TagSetCache AddressDirty bitWrite in memoryscratch: the set after it, LRU first
1Write[16]10Cold-start Miss[1000]2[0]10a1No16
2Write[14]10Cold-start Miss[0111]2[0]10b1No16 14
3Write[07]10Cold-start Miss[0011]2[1]10e1No7
4Read[14]10Hit[0111]2[0]10b1No16 14
5Write[16]10Hit[1000]2[0]10a1No14 16
6Read[12]10Cold-start Miss[0110]2[0]10c0No14 16 12
7Write[18]10Cold-start Miss[1001]2[0]10d1No14 16 12 18
8Read[06]10Cold-start Miss (replaces LRU 14)[0011]2[0]10b0Yes (14)16 12 18 6
9Write[04]10Cold-start Miss (replaces LRU 16)[0010]2[0]10a1Yes (16)12 18 6 4
10Write[30]10Cold-start Miss (replaces LRU 12, clean)[1111]2[0]10c1No18 6 4 30

Miss rate = 8 / 10 = 80 % (two hits: accesses 4 and 5). Final cache: a = 4 (dirty), b = 6, c = 30 (dirty), d = 18 (dirty), e = 7 (dirty), f–h empty.

The paper's own exercise (15 July 2024, Exercise 3); this table is a computed model answer, as the official solution is not in the course material. Rows 8–10 are where it is decided: the LRU victim (14, not 16 — FIFO would pick 16), the dirty bit 0 after the read fill of row 8, and "Write in memory" Yes only for the dirty victims. Every miss here is a first reference to its block, hence Cold-start by the 3C definition of the notes (p. 319). Rows 8–10 also evict from a full set while set 1 still has free lines, which the notes' wording for a conflict miss ("even if the cache is not full") could seem to fit; no official solution settles the label, so write the reason next to the type ("replaces LRU block 14, dirty") and either reading is covered.

Same 32-block main memory and the same sequence of memory accesses (Write 16, Write 14, Write 07, Read 14, Write 16, Read 12, Write 18, Read 06, Write 04, Write 30), but now a 2-way set associative 8-block cache, again write allocate with write-back and LRU; Cache Addresses [a … h], tags in binary. How many sets? Complete the table and compute the miss rate. Which blocks are written back to memory?

Show model answer

Sets = 8 / 2 = 4: set = address mod 4 (the two last bits), tag = address div 4 (3 bits). Set 0 = a, b; set 1 = c, d; set 2 = e, f; set 3 = g, h.

Type of memory accessMemory AddressHit/ Miss TypeCache TagSetCache AddressDirty bitWrite in memoryscratch: the set after it, LRU first
1Write[16]10Cold-start Miss[100]2[0]10a1No16
2Write[14]10Cold-start Miss[011]2[2]10e1No14
3Write[07]10Cold-start Miss[001]2[3]10g1No7
4Read[14]10Hit[011]2[2]10e1No14
5Write[16]10Hit[100]2[0]10a1No16
6Read[12]10Cold-start Miss[011]2[0]10b0No16 12
7Write[18]10Cold-start Miss[100]2[2]10f1No14 18
8Read[06]10Cold-start Miss (replaces LRU 14)[001]2[2]10e0Yes (14)18 6
9Write[04]10Cold-start Miss (replaces LRU 16)[001]2[0]10a1Yes (16)12 4
10Write[30]10Cold-start Miss (replaces LRU 18)[111]2[2]10f1Yes (18)6 30

Miss rate = 8 / 10 = 80 %. Written back: blocks 14, 16 and 18.

A variant built on the paper's numbers to check that you do not copy the 4-way table: with four sets of two ways, sets 0 and 2 fill up sooner, and one more dirty block (18) is written back. Set 1 is never used, because no address of the sequence is 1 mod 4 — which is legal; do not "move" a block into a free line of another set. Recompute both columns from the address every time: the tag now has 3 digits.

A byte-addressed processor with 32-bit addresses has a 64 KB 4-way set-associative data cache with 64-byte blocks. How many bits does the tag have?

Show model answer

Offset = log2(64) = 6 bits; blocks = 65 536 / 64 = 1024; sets = 1024 / 4 = 256, so index = 8 bits; tag = 32 − 8 − 6 = 18 bits. Check: 32 − log2(64 KB / 4) = 32 − 14 = 18.

The two usual slips: forgetting to divide the lines by the ways (index 10, tag 16) and taking the block size in words when the machine is byte-addressed. A common follow-up asks for the storage the cache really needs: every line keeps V, D and its tag besides the data, 1024 × (1 + 1 + 18 + 512) = 544 768 bits; the 20 480 bits of V, D and tags add about 4 % to the 64 KB of data (the LRU state of the sets comes on top).

Which statements about a write-back cache are correct? (MULTIPLE ANSWERS)

  • Answer 1: Every store updates main memory immediately. That is write-through; write-back updates only the cache line.
  • Answer 2: A block is written to main memory only when it is evicted while dirty. The dirty bit records that the cached copy is newer; the eviction writes it back.
  • Answer 3: It generates less memory traffic than write-through when the same block is written many times. Many stores to one block become a single block write at eviction.
  • Answer 4: Main memory always holds the most recent value. Memory is stale while a dirty copy sits in the cache.
  • Answer 5: It is usually combined with write allocate. Allocation brings the block in, so later writes to it hit in the cache.
Show model answer

Answers 2, 3 and 5. Answers 1 and 4 describe write-through.

A multiple-answer quiz in the June 2026 format. Answer 4 is the common trap: in a write-back cache it is the cache, not memory, that holds the newest copy of a dirty block.

True or false: with LRU replacement, the block evicted from a full set is the one that was brought into the set first. (True/False)

  • True "Brought in first" is FIFO. LRU looks at the last use, which a later hit can renew.
  • False LRU evicts the block whose most recent access is the oldest; in July 2024, access 8, that is block 14, while block 16 arrived first.
Show model answer

False. LRU evicts the least recently used block; the block that arrived first is the FIFO victim. Before access 8 of the July 2024 sequence, set 0 holds 16 (in at 1, last used 5) and 14 (in at 2, last used 4): FIFO evicts 16, LRU evicts 14.

True/False is one of the quiz forms of the papers (June 2025, Quiz 3), and a near-definition like this one is its natural target. Back the answer with a two-block counter-example; it takes one line.

Check yourself

A 4-way set-associative cache has 8 blocks, main memory has 32. Where may block 22 go?

  • Only line g 22 mod 8 = 6 is the direct-mapped rule. With 4 ways there are only 2 sets, and the rule is mod #sets.
  • Any of lines a–d 22 mod 2 = 0: set 0, any of its four ways; the tag is 22 div 2 = 11 = 1011.
  • Any of lines e–h Those form set 1, which takes the odd blocks.
  • Any line That is fully associative. Here the set is fixed by the address; only the way is free.

Cache size and block size stay the same; the associativity doubles from 4 to 8 ways. What changes? (Several answers.)

  • The index loses one bit Twice the ways means half the sets: log2 #sets drops by one.
  • The tag gains one bit The address width is fixed, so the bit the index loses goes to the tag.
  • The number of comparators doubles One comparator per way: the 8 tags of a set are compared at once.
  • The offset loses one bit The offset depends only on the block size, which did not change.
  • The number of lines doubles Same cache size, same block size: same number of lines, grouped differently.

In a 2-way set-associative cache, what does the hardware compare to decide whether an access hits?

  • The address tag with the stored tags of the two ways of the selected set, each together with its valid bit The index selects the set; two comparators check the tags; V = 1 is required.
  • The address index with the index stored in each line No line stores its index: its position is the index. The notes' "if index matches" is wrong on this point.
  • The whole address with every address stored in the cache Index and offset are implied by the position; only tags are stored and compared.
  • The address tag with every tag of the cache That is the fully associative search; a set-associative cache looks in one set only.

A write-back, write-allocate cache. In which accesses is the exam table's "Write in memory" Yes? (Several answers.)

  • A read miss whose victim is dirty The dirty victim must be written back before its line is reused — the "read that writes" of access 8.
  • A write miss whose victim is dirty Same reason as for a read: the victim's newer data must reach memory — access 9.
  • A write hit Write-back only sets the dirty bit; memory waits for the eviction.
  • A write miss into a free line Nothing is evicted, and the write itself stays in the cache.
  • A read miss whose victim is clean A clean victim equals its memory copy, so it is simply overwritten, whatever the access that evicts it: block 12, evicted clean at access 10, gets No in the paper's table.

A full 4-way set holds A (arrived at access 1, last used at 9), B (arrived 3, last used 4), C (arrived 5, last used 5) and D (arrived 7, last used 8). A miss needs a victim. Which blocks do LRU and FIFO evict?

  • LRU evicts B, FIFO evicts A B's last use (4) is the oldest; A's arrival (1) is the oldest.
  • Both evict A A arrived first, but it was used at access 9, the most recent use of all: LRU keeps it.
  • Both evict B FIFO does not look at uses, only at arrivals: A came in first.
  • LRU evicts C, FIFO evicts A C was last used at 5, but B at 4 — earlier.

A write-through, no-write-allocate cache gets a write miss. What happens?

  • The word is written to memory; the cache is not changed Write-around: no block is fetched, so the next access to the block misses again.
  • The block is fetched, then written both in the cache and in memory That is write-through with write-allocate.
  • The block is fetched and marked dirty That is write-back with write-allocate; write-through has no dirty bit.
  • The write waits until the block has been brought into the cache Nothing is brought in: no-write-allocate goes straight to memory.

Why is the tag the high-order part of the block address, and the set the low-order part?

  • So that consecutive blocks fall into different sets Neighbouring blocks differ in their low bits; taking the set from them spreads an array over the whole cache.
  • Because the high-order bits change most often The opposite: the low-order bits change from one block to the next.
  • Because the tag must be as short as possible The tag has the same length whichever bits are chosen for the index.
  • Because comparators can only compare high-order bits A comparator compares whatever bits it is given.

Picture an array of eight consecutive blocks in the 8-line direct-mapped cache: with the low bits as index they fill lines 0 to 7; with the high bits they would all fight for one line.

Key take-aways

What to watch and read