@@ -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