Skip to content

Commit 7ff22ab

Browse files
Add miss path for lookup
1 parent b1cec2d commit 7ff22ab

2 files changed

Lines changed: 173 additions & 17 deletions

File tree

coreblocks/cache/dcache.py

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ def elaborate(self, platform):
9494

9595
m.submodules.mem = self.mem = DCacheMemory(self.params)
9696

97+
# rr_way = Signal(range(self.params.num_of_ways)) # Round-robin state
98+
9799
flush_start = Signal()
98100
flush_finish = Signal()
99101
needs_writeback = Signal() # if we missed and victim is dirty, writeback the victim and load new line
@@ -242,10 +244,12 @@ def _():
242244
# End the writeback
243245
# Runs if FSM is WRITEBACK and refiller.accept_writeback is ready
244246
with Transaction(name="WritebackEnd").body(m, ready=fsm.ongoing("WRITEBACK")):
245-
result = self.refiller.accept_writeback(m)
246-
247+
# result = self.refiller.accept_writeback(m)
247248
# TODO: handle error
248249

250+
with m.If(~wb_triggered_by_flush):
251+
self.refiller.start_refill(m, addr=self.serialize_addr(refill_addr))
252+
249253
# Invalidate the written-back way
250254
m.d.comb += [
251255
self.mem.way_wr_en.eq(1 << wb_way),
@@ -263,6 +267,8 @@ def _():
263267
# ---------- LOOKUP ----
264268

265269
with Transaction(name="Lookup").body(m, ready=fsm.ongoing("LOOKUP") & pending_req_valid & lookup_valid):
270+
# If cache hit: set dirty bit if store -> return data
271+
# If cache miss: if victim has dirty bit, writeback cache line -> refill -> return data
266272
tag_hit = Array(
267273
self.mem.tag_rd_data[i].valid & (self.mem.tag_rd_data[i].tag == lookup_addr.tag)
268274
for i in range(self.params.num_of_ways)
@@ -284,7 +290,6 @@ def _():
284290

285291
with m.If(tag_hit_any):
286292
with m.If(pending_req.store):
287-
# TODO: For now, do not check if victim is dirty.
288293
m.d.comb += [
289294
self.mem.way_wr_en.eq(1 << hit_way),
290295
self.mem.data_wr_en.eq(1),
@@ -315,18 +320,50 @@ def _():
315320
]
316321

317322
with m.Else():
318-
aligned_addr = self.serialize_addr(lookup_addr) & ~((1 << self.params.offset_bits) - 1)
319-
self.refiller.start_refill(m, aligned_addr)
320-
m.d.comb += needs_refill.eq(1)
321-
m.d.sync += [
322-
refill_addr.offset.eq(0),
323-
refill_addr.index.eq(lookup_addr.index),
324-
refill_addr.tag.eq(lookup_addr.tag),
325-
refill_way.eq(0), # TODO: choose invalid way first, otherwise replacement policy
326-
refill_error.eq(0),
327-
lookup_valid.eq(0),
323+
# we choose way=0 for now (TODO: change to round-robin)
324+
# we check if dirty, if yes, change FSM to writeback
325+
# then, we refill
326+
# then, lookup transaction starts again, now with proper refilled cache line
327+
victim_way = 0
328+
victim_tag_data = self.mem.tag_rd_data[victim_way]
329+
victim_addr = Signal(self.addr_layout)
330+
m.d.comb += [
331+
victim_addr.offset.eq(0),
332+
victim_addr.index.eq(lookup_addr.index),
333+
victim_addr.tag.eq(victim_tag_data.tag),
328334
]
329335

336+
aligned_refill_addr = self.serialize_addr(lookup_addr) & ~((1 << self.params.offset_bits) - 1)
337+
338+
with m.If(victim_tag_data.valid & victim_tag_data.dirty):
339+
# Writeback, then Refill
340+
self.refiller.start_writeback(m, addr=self.serialize_addr(victim_addr))
341+
m.d.comb += needs_writeback.eq(1)
342+
m.d.sync += [
343+
wb_way.eq(victim_way),
344+
wb_index.eq(lookup_addr.index),
345+
wb_word_counter.eq(0),
346+
wb_triggered_by_flush.eq(0),
347+
refill_addr.offset.eq(0),
348+
refill_addr.index.eq(lookup_addr.index),
349+
refill_addr.tag.eq(lookup_addr.tag),
350+
refill_way.eq(victim_way),
351+
refill_error.eq(0),
352+
lookup_valid.eq(0),
353+
]
354+
with m.Else():
355+
# Refill
356+
self.refiller.start_refill(m, aligned_refill_addr)
357+
m.d.comb += needs_refill.eq(1)
358+
m.d.sync += [
359+
refill_addr.offset.eq(0),
360+
refill_addr.index.eq(lookup_addr.index),
361+
refill_addr.tag.eq(lookup_addr.tag),
362+
refill_way.eq(victim_way),
363+
refill_error.eq(0),
364+
lookup_valid.eq(0),
365+
]
366+
330367
# ------------- REFILL ---------
331368
with Transaction(name="Refill").body(m, ready=fsm.ongoing("REFILL")):
332369
ret = self.refiller.accept_refill(m)

test/cache/test_dcache.py

Lines changed: 123 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,15 @@ def elaborate(self, platform):
4949
self.issue_req = TestbenchIO(AdapterTrans.create(self.cache.issue_req))
5050
self.accept_res = TestbenchIO(AdapterTrans.create(self.cache.accept_res))
5151
self.flush_cache = TestbenchIO(AdapterTrans.create(self.cache.flush))
52+
self.provide_writeback_data = TestbenchIO(AdapterTrans.create(self.cache.provide_writeback_data))
5253

5354
return ModuleConnector(
5455
refiller=self.refiller,
5556
cache=self.cache,
5657
issue_req=self.issue_req,
5758
accept_res=self.accept_res,
5859
flush_cache=self.flush_cache,
60+
provide_writeback_data=self.provide_writeback_data,
5961
)
6062

6163

@@ -73,6 +75,9 @@ def setup_method(self) -> None:
7375
self.m = DCacheTestCircuit(self.gen_params)
7476
self.refill_start_calls = deque()
7577
self.refill_responses = deque()
78+
self.writeback_start_calls = deque()
79+
self.writeback_accept_responses = deque()
80+
self.allow_writeback_accept = False
7681

7782
@def_method_mock(lambda self: self.m.refiller.start_refill_mock, enable=lambda self: True)
7883
def start_refill_unexpected(self, addr):
@@ -98,15 +103,20 @@ def eff():
98103
def start_writeback_unexpected(self, addr):
99104
@MethodMock.effect
100105
def eff():
101-
raise AssertionError(f"unexpected start_writeback call for address 0x{addr:08x}")
106+
self.writeback_start_calls.append(addr)
102107

103-
@def_method_mock(lambda self: self.m.refiller.accept_writeback_mock, enable=lambda self: True)
108+
@def_method_mock(
109+
lambda self: self.m.refiller.accept_writeback_mock,
110+
enable=lambda self: self.allow_writeback_accept and bool(self.writeback_accept_responses),
111+
)
104112
def accept_writeback_unexpected(self):
105113
@MethodMock.effect
106114
def eff():
107-
raise AssertionError("unexpected accept_writeback call")
115+
if not self.writeback_accept_responses:
116+
raise AssertionError("unexpected accept_writeback call")
117+
self.writeback_accept_responses.popleft()
108118

109-
return {"error": 0}
119+
return self.writeback_accept_responses[0]
110120

111121
def split_addr(self, addr: int) -> tuple[int, int, int]:
112122
index = (addr >> self.cp.offset_bits) & (self.cp.num_of_sets - 1)
@@ -165,6 +175,22 @@ def queue_refill_line(self, line_addr: int, words: list[int], *, error: int = 0)
165175
if error:
166176
break
167177

178+
async def collect_writeback_line(self, sim: TestbenchContext, *, words_in_line: int) -> list[int]:
179+
words = []
180+
await sim.tick()
181+
for _ in range(words_in_line):
182+
resp = await self.m.provide_writeback_data.call(sim)
183+
words.append(resp["data"])
184+
await sim.tick()
185+
return words
186+
187+
async def wait_until(self, sim: TestbenchContext, pred, *, max_ticks: int = 50):
188+
for _ in range(max_ticks):
189+
if pred():
190+
return
191+
await sim.tick()
192+
raise AssertionError("condition not met in time")
193+
168194
def read_tag_entry(self, sim: TestbenchContext, *, way: int, index: int) -> dict[str, int]:
169195
raw_tag = sim.get(self.m.cache.mem.tag_mems[way].data[index]) # type: ignore[arg-type]
170196
return {
@@ -344,3 +370,96 @@ async def cache_process(sim: TestbenchContext):
344370

345371
with self.run_simulation(self.m) as sim:
346372
sim.add_testbench(cache_process)
373+
374+
def test_load_dirty_miss_writebacks_old_line_then_refills_and_replays_request(self):
375+
async def cache_process(sim: TestbenchContext):
376+
old_base_addr = 0x00000100
377+
old_words = [0xDEADBEEF, 0x11223344, 0x55667788, 0x99AABBCC]
378+
new_base_addr = 0x00000200
379+
new_words = [0xAAAABBBB, 0xCCCCDDDD, 0xEEEEFFFF, 0x12345678]
380+
381+
await self.wait_for_initial_flush(sim)
382+
await self.preload_line(sim, old_base_addr, old_words, way=0, dirty=1)
383+
self.queue_refill_line(new_base_addr, new_words)
384+
self.writeback_accept_responses.append({"error": 0})
385+
386+
await self.m.issue_req.call(
387+
sim, addr=new_base_addr + self.cp.word_width_bytes, data=0, byte_mask=0, store=0
388+
)
389+
390+
await self.wait_until(sim, lambda: len(self.writeback_start_calls) == 1)
391+
assert list(self.writeback_start_calls) == [old_base_addr]
392+
assert not self.refill_start_calls
393+
394+
written_back_words = await self.collect_writeback_line(sim, words_in_line=self.cp.words_in_line)
395+
assert written_back_words == old_words
396+
assert not self.refill_start_calls
397+
398+
self.allow_writeback_accept = True
399+
resp = await self.m.accept_res.call(sim)
400+
401+
assert list(self.refill_start_calls) == [new_base_addr]
402+
assert resp["error"] == 0
403+
assert resp["data"] == new_words[1]
404+
assert not self.refill_responses
405+
406+
_, index, _ = self.split_addr(new_base_addr)
407+
new_tag, _, _ = self.split_addr(new_base_addr)
408+
stored_tag = self.read_tag_entry(sim, way=0, index=index)
409+
hit_resp = await self.call_cache(sim, addr=new_base_addr + 2 * self.cp.word_width_bytes)
410+
411+
assert stored_tag["valid"] == 1
412+
assert stored_tag["tag"] == new_tag
413+
assert hit_resp["error"] == 0
414+
assert hit_resp["data"] == new_words[2]
415+
416+
with self.run_simulation(self.m) as sim:
417+
sim.add_testbench(cache_process)
418+
419+
def test_store_dirty_miss_writebacks_old_line_then_refills_and_replays_store(self):
420+
async def cache_process(sim: TestbenchContext):
421+
old_base_addr = 0x00000140
422+
old_words = [0xCAFEBABE, 0x0BADF00D, 0x01020304, 0xA0B0C0D0]
423+
new_base_addr = 0x00000240
424+
new_words = [0x10203040, 0x50607080, 0x90A0B0C0, 0xD0E0F000]
425+
store_addr = new_base_addr + self.cp.word_width_bytes
426+
store_data = 0x11223344
427+
byte_mask = 0b0011
428+
429+
await self.wait_for_initial_flush(sim)
430+
await self.preload_line(sim, old_base_addr, old_words, way=0, dirty=1)
431+
self.queue_refill_line(new_base_addr, new_words)
432+
self.writeback_accept_responses.append({"error": 0})
433+
434+
await self.m.issue_req.call(sim, addr=store_addr, data=store_data, byte_mask=byte_mask, store=1)
435+
436+
await self.wait_until(sim, lambda: len(self.writeback_start_calls) == 1)
437+
assert list(self.writeback_start_calls) == [old_base_addr]
438+
assert not self.refill_start_calls
439+
440+
written_back_words = await self.collect_writeback_line(sim, words_in_line=self.cp.words_in_line)
441+
assert written_back_words == old_words
442+
assert not self.refill_start_calls
443+
444+
self.allow_writeback_accept = True
445+
resp = await self.m.accept_res.call(sim)
446+
447+
assert list(self.refill_start_calls) == [new_base_addr]
448+
assert resp["error"] == 0
449+
assert resp["data"] == 0
450+
assert not self.refill_responses
451+
452+
await sim.tick()
453+
454+
new_tag, index, word_offset = self.split_addr(store_addr)
455+
expected_word = self.merge_word(new_words[1], store_data, byte_mask)
456+
stored_word = self.read_data_word(sim, way=0, index=index, word_offset=word_offset)
457+
stored_tag = self.read_tag_entry(sim, way=0, index=index)
458+
459+
assert stored_word == expected_word
460+
assert stored_tag["valid"] == 1
461+
assert stored_tag["dirty"] == 1
462+
assert stored_tag["tag"] == new_tag
463+
464+
with self.run_simulation(self.m) as sim:
465+
sim.add_testbench(cache_process)

0 commit comments

Comments
 (0)