def setUp(self): self.fake_bus = FakeBus() self.fake_tracker = FakeTracker() self.default_cache = Cache( 0, # Cache id. CachingTest._NUMBER_OF_CACHE_LINES, CachingTest._SIZE_OF_CACHE_LINE, self.fake_bus, self.fake_tracker, debug_mode=False) # Simulations for the test_trace tests. self.sc_simulation = SimulationEnvironment( CachingTest._NUMBER_OF_PROCESSORS, CachingTest._NUMBER_OF_CACHE_LINES, CachingTest._SIZE_OF_CACHE_LINE, "SC", debug_mode=True, # Turn on to force consistency checks. ) self.tso_simulation = SimulationEnvironment( CachingTest._NUMBER_OF_PROCESSORS, CachingTest._NUMBER_OF_CACHE_LINES, CachingTest._SIZE_OF_CACHE_LINE, "TSO", debug_mode=True, # Turn on to force consistency checks. write_buffer_size=32, retire_at_count=1)
def setUp(self): self.simulation = SimulationEnvironment( SimulationTest._NUMBER_OF_PROCESSORS, SimulationTest._NUMBER_OF_CACHE_LINES, SimulationTest._SIZE_OF_CACHE_LINE, # Always turn on debug mode, so that the sanity check in # SimulationEnvironment.simulate() execute. debug_mode=True)
class CachingTest(unittest.TestCase): """Tests for the caching module.""" # Default configuration. Individual tests may override. _NUMBER_OF_PROCESSORS = 4 _NUMBER_OF_CACHE_LINES = 128 _SIZE_OF_CACHE_LINE = 4 def setUp(self): self.fake_bus = FakeBus() self.fake_tracker = FakeTracker() self.default_cache = Cache( 0, # Cache id. CachingTest._NUMBER_OF_CACHE_LINES, CachingTest._SIZE_OF_CACHE_LINE, self.fake_bus, self.fake_tracker, debug_mode=False) # Simulations for the test_trace tests. self.sc_simulation = SimulationEnvironment( CachingTest._NUMBER_OF_PROCESSORS, CachingTest._NUMBER_OF_CACHE_LINES, CachingTest._SIZE_OF_CACHE_LINE, "SC", debug_mode=True, # Turn on to force consistency checks. ) self.tso_simulation = SimulationEnvironment( CachingTest._NUMBER_OF_PROCESSORS, CachingTest._NUMBER_OF_CACHE_LINES, CachingTest._SIZE_OF_CACHE_LINE, "TSO", debug_mode=True, # Turn on to force consistency checks. write_buffer_size=32, retire_at_count=1) def test_initialization(self): """Tests that a Cache is initialized properly.""" # First, the default cache. 128 lines maps to 7 slot bits, 4 words per line # maps to 2 offset bits. self.assertEqual(self.default_cache.cache_id, 0) self.assertEqual(self.default_cache.slot_bits, 7) self.assertEqual(self.default_cache.offset_bits, 2) self.assertEqual(self.default_cache.bus, self.fake_bus) self.assertEqual(self.default_cache.tracker, self.fake_tracker) # A different sized cache. 512 lines ==> 9 slot bits, 1 word per line ==> 0 # offset bits. cache = Cache(1, 512, 1, self.fake_bus, self.fake_tracker, debug_mode=False) self.assertEqual(cache.cache_id, 1) self.assertEqual(cache.slot_bits, 9) self.assertEqual(cache.offset_bits, 0) # One more. 32 lines ==> 5 slot bits, 32 word per line ==> 5 offset bits. cache = Cache(2, 32, 32, self.fake_bus, self.fake_tracker, debug_mode=False) self.assertEqual(cache.cache_id, 2) self.assertEqual(cache.slot_bits, 5) self.assertEqual(cache.offset_bits, 5) # Finally, test the error-throwing cases: non-powers of two. with self.assertRaises(ValueError): Cache(2, 15, 32, self.fake_bus, self.fake_tracker, debug_mode=False) with self.assertRaises(ValueError): Cache(2, 32, 15, self.fake_bus, self.fake_tracker, debug_mode=False) with self.assertRaises(ValueError): Cache(2, -2, 32, self.fake_bus, self.fake_tracker, debug_mode=False) with self.assertRaises(ValueError): Cache(2, 32, -2, self.fake_bus, self.fake_tracker, debug_mode=False) def test_address_placement(self): """Tests that addresses are mapped to the correct slot. Note that this overlaps with testing actual MSI protocol, but that is not the aim here: only the actual breakdown of the address is of interest. The check for SHARED is only to make sure that the line is actually in the cache.""" # First test the default setup: 128 lines, 4 words per line. # Address 5: slot 1, tag 0. self.default_cache.read(5) line = self.default_cache.get_cache_line(1) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 0) # Address 522: slot 2, tag 1. self.default_cache.read(522) line = self.default_cache.get_cache_line(2) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 1) # Address 523: still slot 2, tag 1. self.default_cache.read(523) line = self.default_cache.get_cache_line(2) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 1) # Address 2571: also slot 2, but tag 5. self.default_cache.read(2571) line = self.default_cache.get_cache_line(2) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 5) # Now test another cache: 16 lines, 8 words per line. cache = Cache(1, 16, 8, self.fake_bus, self.fake_tracker, debug_mode=False) # Address 5: slot 0, tag 0. cache.read(5) line = cache.get_cache_line(0) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 0) # Address 522: slot 1, tag 4. cache.read(522) line = cache.get_cache_line(1) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 4) # Address 527: still slot 1, tag 4. cache.read(527) line = cache.get_cache_line(1) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 4) # Address 528: slot 2, still tag 4. cache.read(528) line = cache.get_cache_line(2) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 4) # Address 2571: also slot 1, but tag 20. cache.read(2571) line = cache.get_cache_line(1) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 20) def test_local_accesses(self): """Tests that local accesses adhere to the MSI protocol.""" # State transitions should be: # INVALID + read ==> read_miss + SHARED. # INVALID + write ==> write_miss + MODIFIED. # SHARED + read ==> read_hit + SHARED. # SHARED + write ==> write_miss + MODIFIED. # MODIFIED + read ==> read_hit + MODIFIED. # MODIFIED + write ==> write_hit + MODIFIED. # INVALID + read. hit = self.default_cache.read(10) self.assertFalse(hit) line = self.default_cache.get_cache_line(2) # Address 10 ==> slot 2. self.assertEqual(line.state, SlotState.SHARED) self.assertFalse(line.written_to) # INVALID + write. hit = self.default_cache.write(20) self.assertFalse(hit) line = self.default_cache.get_cache_line(5) # Address 20 ==> slot 5. self.assertEqual(line.state, SlotState.MODIFIED) self.assertTrue(line.written_to) # SHARED + read. self.default_cache.read(30) # Prime the cache. hit = self.default_cache.read(31) self.assertTrue(hit) line = self.default_cache.get_cache_line(7) # Address 31 ==> slot 7. self.assertEqual(line.state, SlotState.SHARED) self.assertFalse(line.written_to) # SHARED + write. self.default_cache.read(40) # Prime the cache. hit = self.default_cache.write(41) self.assertFalse(hit) line = self.default_cache.get_cache_line(10) # Address 41 ==> slot 10. self.assertEqual(line.state, SlotState.MODIFIED) self.assertTrue(line.written_to) # MODIFIED + read self.default_cache.write(50) # Prime the cache. hit = self.default_cache.read(51) self.assertTrue(hit) line = self.default_cache.get_cache_line(12) # Address 51 ==> slot 12. self.assertEqual(line.state, SlotState.MODIFIED) self.assertTrue(line.written_to) # MODIFIED + write self.default_cache.write(60) # Prime the cache. hit = self.default_cache.write(61) self.assertTrue(hit) line = self.default_cache.get_cache_line(15) # Address 61 ==> slot 15. self.assertEqual(line.state, SlotState.MODIFIED) self.assertTrue(line.written_to) # Mismatching tags in any non-INVALID state should be equivalent to INVALID. # SHARED + read. self.default_cache.read(30) # Prime the cache. hit = self.default_cache.read(2590) # Access a different tag. self.assertFalse(hit) line = self.default_cache.get_cache_line( 7) # Address 30|2590 ==> slot 7. self.assertFalse(line.written_to) # SHARED + write. self.default_cache.read(40) # Prime the cache. hit = self.default_cache.write(10280) # Access a different tag. self.assertFalse(hit) line = self.default_cache.get_cache_line( 10) # Address 40|10280 ==> slot 10. self.assertTrue(line.written_to) # MODIFIED + read self.default_cache.write(50) # Prime the cache. hit = self.default_cache.read(5682) # Access a different tag. self.assertFalse(hit) line = self.default_cache.get_cache_line( 12) # Address 50|5682 ==> slot 12. self.assertFalse(line.written_to) # 5682 has only been read. # MODIFIED + write self.default_cache.write(60) # Prime the cache. hit = self.default_cache.write(1596) # Access a different tag. self.assertFalse(hit) line = self.default_cache.get_cache_line( 15) # Address 60|1596 ==> slot 12. self.assertTrue(line.written_to) def test_remote_accesses(self): """Tests that remote accesses adhere to the MSI protocol.""" # State transitions should be: # INVALID + remote read miss ==> INVALID. # INVALID + remote write miss ==> INVALID. # SHARED + remote read miss ==> SHARED. # SHARED + remote write miss ==> INVALID. # MODIFIED + remote read miss ==> SHARED. # MODIFIED + remote write miss ==> INVALID. # INVALID + remote read miss. self.default_cache.notify_read_miss(10) line = self.default_cache.get_cache_line(2) # Address 10 ==> slot 2. self.assertEqual(line.state, SlotState.INVALID) self.assertEqual(line.previous_state, None) # INVALID + remote write miss. self.default_cache.notify_write_miss(20) line = self.default_cache.get_cache_line(5) # Address 20 ==> slot 5. self.assertEqual(line.state, SlotState.INVALID) self.assertEqual(line.previous_state, None) # SHARED + remote read miss. self.default_cache.read(30) # Prime the cache. self.default_cache.notify_read_miss(31) line = self.default_cache.get_cache_line(7) # Address 31 ==> slot 7. self.assertEqual(line.state, SlotState.SHARED) # The previous_state variable tracks the previous state for coherence-caused # changes, so won't have changed here. self.assertEqual(line.previous_state, SlotState.INVALID) # SHARED + remote write miss. self.default_cache.read(40) # Prime the cache. self.default_cache.notify_write_miss(41) line = self.default_cache.get_cache_line(10) # Address 41 ==> slot 10. self.assertEqual(line.state, SlotState.INVALID) self.assertEqual(line.previous_state, SlotState.SHARED) # MODIFIED + remote read miss. self.default_cache.write(50) # Prime the cache. self.default_cache.notify_read_miss(51) line = self.default_cache.get_cache_line(12) # Address 51 ==> slot 12. self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.previous_state, SlotState.MODIFIED) # MODIFIED + remote write miss. self.default_cache.write(60) # Prime the cache. self.default_cache.notify_write_miss(61) line = self.default_cache.get_cache_line(15) # Address 61 ==> slot 15. self.assertEqual(line.state, SlotState.INVALID) self.assertEqual(line.previous_state, SlotState.MODIFIED) # Mismatching tags should not cause a change. # SHARED + write: shouldn't go to INVALID. self.default_cache.read(30) # Prime the cache. self.default_cache.notify_write_miss( 2590) # Write miss for different tag. line = self.default_cache.get_cache_line( 7) # Address 30|2590 ==> slot 7. self.assertEqual(line.state, SlotState.SHARED) # MODIFIED + read: shouldn't go to SHARED. self.default_cache.write(40) # Prime the cache. self.default_cache.notify_read_miss( 10280) # Read miss for different tag. line = self.default_cache.get_cache_line( 10) # Address 40|10280 ==> slot 10. self.assertEqual(line.state, SlotState.MODIFIED) def test_write_tracking(self): """Tests that writes to a cache line are correctly tracked.""" # Basic case: line not written to. self.default_cache.read(10) self.default_cache.read(11) line = self.default_cache.get_cache_line(2) # Address 10 ==> slot 2. self.assertFalse(line.written_to) # Basic case: line written to. self.default_cache.read(20) self.default_cache.write(21) self.default_cache.read(21) line = self.default_cache.get_cache_line(5) # Address 20 ==> slot 5. self.assertTrue(line.written_to) # Basic case: line written to, then flushed. self.default_cache.read(30) self.default_cache.write(31) self.default_cache.read(543) # Cache slot flushed. line = self.default_cache.get_cache_line( 7) # Address 30|543 ==> slot 7. self.assertFalse(line.written_to) # Basic case: line written to, then flushed, then re-filled. self.default_cache.read(40) self.default_cache.write(41) self.default_cache.read(553) # Cache slot flushed. self.default_cache.read(40) # Re-filled. line = self.default_cache.get_cache_line( 10) # Address 50|553 ==> slot 10. self.assertFalse(line.written_to) # More complex case. Line written to, then flushed by external processor. self.default_cache.write(50) self.default_cache.notify_write_miss(51) # Flush. line = self.default_cache.get_cache_line(12) # Address 50 ==> slot 12 self.assertTrue(line.written_to) # More complex case. Line written to, then set to SHARED by external processor. self.default_cache.write(60) self.default_cache.notify_read_miss(61) # Set to SHARED. line = self.default_cache.get_cache_line(15) # Address 60 ==> slot 15 self.assertTrue(line.written_to) def test_sc_read_latency(self): """Tests the measurements of read-latency in an SC cache.""" # Cache with 16 lines, 8 words/line. cache = Cache(1, 16, 8, self.fake_bus, self.fake_tracker, False) # Test object in cache. cache.read(1) # Prime cache. cache.latency = 0 cache.read(1) self.assertEqual(cache.latency, 2) cache.latency = 0 # Test line returned by other processor. cache.read(9) self.assertEqual(cache.latency, 22) cache.latency = 0 # Test line returned by main memory. # Duck-punch the fake bus to claim that it read from memory. # (Don't you love Python? Such abuse! :D) old_read_miss = FakeBus.read_miss def new_read_miss(self, cache_id, address): return True FakeBus.read_miss = new_read_miss cache.read(18) self.assertEqual(cache.latency, 222) FakeBus.read_miss = old_read_miss def test_tso_read_latency(self): """Tests the measurements of read-latency in a TSO cache.""" # Cache with 16 lines, 8 words/line, 4-write buffer with retire-at-2. cache = TSOCache(1, 16, 8, self.fake_bus, self.fake_tracker, 4, 2, False) # First test that write buffers are snooped correctly. cache.write(5) cache.read(5) self.assertEqual(cache.latency, 1) cache.latency = 0 # Test value that is in the L1 cache. cache.read(4) # Prime the cache. cache.latency = 0 cache.read(4) self.assertEqual(cache.latency, 3) cache.latency = 0 # Test value returned by other processor. cache.read(19) self.assertEqual(cache.latency, 23) cache.latency = 0 # Test value returned by main memory. # Duck-punch the fake bus to claim that it read from memory. # (Don't you love Python? Such abuse! :D) old_read_miss = FakeBus.read_miss def new_read_miss(self, cache_id, address): return True FakeBus.read_miss = new_read_miss cache.read(26) self.assertEqual(cache.latency, 223) # Restore the FakeBus class. FakeBus.read_miss = old_read_miss def test_sc_write_latency(self): """Tests the measurements of write-latency in an SC cache. All writes in SC take 222 cycles, due to write-through cache.""" # Cache with 16 lines, 8 words/line. cache = Cache(1, 16, 8, self.fake_bus, self.fake_tracker, False) # Test writing to something in cache. cache.write(5) # Prime cache. cache.latency = 0 cache.write(5) self.assertEqual(cache.latency, 222) cache.latency = 0 # Test writing to something that another processor can return. cache.write(9) self.assertEqual(cache.latency, 222) cache.latency = 0 # Test value returned by main memory. # Duck-punch the fake bus to claim that it read from memory. # (Don't you love Python? Such abuse! :D) old_read_miss = FakeBus.read_miss def new_read_miss(self, cache_id, address): return True FakeBus.read_miss = new_read_miss cache.write(20) self.assertEqual(cache.latency, 222) # Restore the FakeBus class. FakeBus.read_miss = old_read_miss def test_tso_write_latency(self): """Tests the measurements of write-latency in a TSO cache. Write latency is complicated in TSO: writes normally do not add to the cache latency, unless they are involved in a write buffer drain. Additionally, any write executing when a write buffer drain happens contributes the remainder of its cycles to the latency.""" # Cache with 16 lines, 8 words/line, 4-write buffer with retire-at-2. cache = TSOCache(1, 16, 8, self.fake_bus, self.fake_tracker, 4, 2, False) # Single write: should be buffered. cache.write(5) self.assertEqual(cache.latency, 0) # A second write: should trip retire at N, but no effect on latency. cache.write(10) self.assertEqual(cache.latency, 0) self.assertEqual(len(cache.write_buffer), 1) # Force a drain. cache.write(15) # 2 writes in buffer cache.write(20) # 3 writes in buffer cache.write(25) # 4 writes in buffer; buffer should be full. cache.write(30) # Should force a drain. # Latency will be 4 writes in buffer, plus the one that was processing. self.assertEqual(cache.latency, 222 * 5) self.assertEqual(len(cache.write_buffer), 1) def test_tso_check_write_buffer(self): """Tests that the check-buffer logic in the TSO cache is correct.""" # Cache with 16 lines, 8 words/line, 4-write buffer with retire-at-2. cache = TSOCache(1, 16, 8, self.fake_bus, self.fake_tracker, 4, 2, False) # First, check that the method does nothing if a write is still processing. cache.write_finishes_at = 15 # Fake a write in progress. cache.write_buffer = [10, 5, 20] # Fake some writes. cache._check_write_buffer() self.assertEqual(cache.write_finishes_at, 15) self.assertEqual(cache.latency, 0) self.assertEqual(cache.write_buffer, [10, 5, 20]) # Check that if a write is not in process but there are less than N (here, # 2) writes in the buffer, it still does nothing. cache.write_finishes_at = None cache.write_buffer = [10] cache._check_write_buffer() self.assertEqual(cache.write_finishes_at, None) self.assertEqual(cache.latency, 0) self.assertEqual(cache.write_buffer, [10]) # Check that if a write is finished and there are less than N (here, 2) # writes in the buffer, the write is cleared but thats it. cache.write_finishes_at = 10 cache.latency = 15 cache.write_buffer = [10] cache._check_write_buffer() self.assertEqual(cache.write_finishes_at, None) self.assertEqual(cache.latency, 15) self.assertEqual(cache.write_buffer, [10]) # Check that if a write is finished and there are N writes in the buffer, # another write is retired, from the correct end of the line. cache.write_finishes_at = 10 cache.latency = 15 cache.write_buffer = [10, 5] cache._check_write_buffer() self.assertEqual(cache.write_finishes_at, 237) self.assertEqual(cache.latency, 15) self.assertEqual(cache.write_buffer, [5]) # Check that if a write is not in progress and there are N writes in the # buffer, another write is retired, from the correct end of the line. cache.write_finishes_at = None cache.latency = 25 cache.write_buffer = [5, 10] cache._check_write_buffer() self.assertEqual(cache.write_finishes_at, 247) self.assertEqual(cache.latency, 25) self.assertEqual(cache.write_buffer, [10]) def test_tso_drain_buffer(self): """Tests that the drain buffer logic in the TSO cache is correct.""" # Cache with 16 lines, 8 words/line, 4-write buffer with retire-at-2. cache = TSOCache(1, 16, 8, self.fake_bus, self.fake_tracker, 4, 2, False) # Check that draining a cache always clears it completely. cache.write_buffer = [1] cache._drain_write_buffer() self.assertEqual(cache.write_buffer, []) cache.write_buffer = [1, 2] cache._drain_write_buffer() self.assertEqual(cache.write_buffer, []) cache.write_buffer = [3, 4, 5, 6, 7] cache._drain_write_buffer() self.assertEqual(cache.write_buffer, []) cache.write_buffer = [] cache._drain_write_buffer() self.assertEqual(cache.write_buffer, []) # Check that when the buffer is drained, any ongoing writes are counted as latency. cache.write_buffer = [] cache.latency = 20 cache.write_finishes_at = 126 cache._drain_write_buffer() self.assertEqual(cache.latency, 126) self.assertEqual(cache.write_finishes_at, None) cache.write_buffer = [10, 20] cache.latency = 20 cache.write_finishes_at = 126 cache._drain_write_buffer() self.assertEqual(cache.latency, 126 + (2 * 222)) self.assertEqual(cache.write_finishes_at, None) # Check that when the buffer is drained, all writes are counted. cache.write_buffer = [10, 20, 30, 40] cache.latency = 0 cache.write_finishes_at = None cache._drain_write_buffer() self.assertEqual(cache.latency, 4 * 222) self.assertEqual(cache.write_finishes_at, None) # Check that when drained, writes execute in the correct order. cache.write_buffer = [10, 138] # Same slot (1), different tag (0 and 1). cache.latency = 0 cache.write_finishes_at = None cache._drain_write_buffer() self.assertEqual(cache.latency, 2 * 222) self.assertEqual(cache.get_cache_line(1).tag, 1) self.assertEqual(cache.get_cache_line(1).state, SlotState.MODIFIED) def test_tso_post_program_drain(self): """Test the post-program drain of the write buffer.""" # Cache with 16 lines, 8 words/line, 4-write buffer with retire-at-2. cache = TSOCache(1, 16, 8, self.fake_bus, self.fake_tracker, 4, 2, False) # Test that nothing changes if theres nothing in the write buffer. cache.latency = 500 cache.notify_finished() self.assertEqual(cache.latency, 500) self.assertEqual(cache.write_buffer, []) self.assertEqual(cache.write_finishes_at, None) # Test that the write buffer is properly cleared if there is something in it. cache.latency = 500 cache.write_buffer = [10, 20] cache.write_finishes_at = 505 cache.notify_finished() self.assertEqual(cache.latency, 505 + (2 * 222)) self.assertEqual(cache.write_buffer, []) self.assertEqual(cache.write_finishes_at, None) def test_sc_read_latency_from_trace(self): """Tests that SC read latency is correctly calculated from a trace. Basically worse than the unittests, but apparently we must provide test traces...""" with capture_output(): self.sc_simulation.simulate("test_traces/sc_read_trace.out") # Get the statistics. stats = self.sc_simulation.tracker.get_general_stats() self.assertEqual(stats["max_latency"], 246) self.assertEqual(stats["max_latency_cache"], 0) def test_sc_write_latency_from_trace(self): """Tests that SC write latency is correctly calculated from a trace. Basically worse than the unittests, but apparently we must provide test traces...""" with capture_output(): self.sc_simulation.simulate("test_traces/sc_write_trace.out") # Get the statistics. stats = self.sc_simulation.tracker.get_general_stats() self.assertEqual(stats["max_latency"], 666) self.assertEqual(stats["max_latency_cache"], 0) def test_tso_read_latency_from_trace(self): """Tests that TSO read latency is correctly calculated from a trace. Basically worse than the unittests, but apparently we must provide test traces...""" with capture_output(): self.tso_simulation.simulate("test_traces/tso_read_trace.out") # Get the statistics. stats = self.tso_simulation.tracker.get_general_stats() self.assertEqual(stats["max_latency"], 693) self.assertEqual(stats["max_latency_cache"], 0) def test_tso_write_latency_from_trace(self): """Tests that TSO write latency is correctly calculated from a trace. Basically worse than the unittests, but apparently we must provide test traces...""" with capture_output(): self.tso_simulation.simulate("test_traces/tso_write_trace.out") # Get the statistics. stats = self.tso_simulation.tracker.get_general_stats() self.assertEqual(stats["max_latency"], 889) self.assertEqual(stats["max_latency_cache"], 0)
class SimulationTest(unittest.TestCase): """Validation tests for the simulator, based on trace files. Tests are generally defined in the trace files themselves; if you do not understand the purpose of a test it is best to refer to the trace file it simulates.""" # All tests use the same simulation configuration. _NUMBER_OF_PROCESSORS = 4 _NUMBER_OF_CACHE_LINES = 128 _SIZE_OF_CACHE_LINE = 4 def setUp(self): self.simulation = SimulationEnvironment( SimulationTest._NUMBER_OF_PROCESSORS, SimulationTest._NUMBER_OF_CACHE_LINES, SimulationTest._SIZE_OF_CACHE_LINE, # Always turn on debug mode, so that the sanity check in # SimulationEnvironment.simulate() execute. debug_mode=True) def test_local_behaviour(self): with capture_output(): self.simulation.simulate("test_traces/local_trace.out") # Grab the cache for processor 0. cache = self.simulation.caches[0] cache_lines = cache.cache_lines # Slot 0 should be SHARED, tag == 0 slot = cache_lines[0] self.assertEqual(slot.state, SlotState.SHARED) self.assertEqual(slot.tag, 0) # Slot 1 should be SHARED, tag == 1 slot = cache_lines[1] self.assertEqual(slot.state, SlotState.SHARED) self.assertEqual(slot.tag, 1) # Slot 2 should be MODIFIED, tag == 2 slot = cache_lines[2] self.assertEqual(slot.state, SlotState.MODIFIED) self.assertEqual(slot.tag, 2) # Slot 3 should be MODIFIED, tag == 3 slot = cache_lines[3] self.assertEqual(slot.state, SlotState.MODIFIED) self.assertEqual(slot.tag, 3) # Slot 4 should be MODIFIED, tag == 4 slot = cache_lines[4] self.assertEqual(slot.state, SlotState.MODIFIED) self.assertEqual(slot.tag, 4) # Slot 5 should be MODIFIED, tag == 5 slot = cache_lines[5] self.assertEqual(slot.state, SlotState.MODIFIED) self.assertEqual(slot.tag, 5) # Slot 6 should be MODIFIED, tag == 6 slot = cache_lines[6] self.assertEqual(slot.state, SlotState.MODIFIED) self.assertEqual(slot.tag, 6) # Slot 7 should INVALID (not accessed), tag == None slot = cache_lines[7] self.assertEqual(slot.state, SlotState.INVALID) self.assertEqual(slot.tag, None) def test_remote_behaviour(self): with capture_output(): self.simulation.simulate("test_traces/remote_trace.out") # Grab the cache for processor 0. cache = self.simulation.caches[0] cache_lines = cache.cache_lines # Slot 0 should be INVALID, tag == 0 slot = cache_lines[0] self.assertEqual(slot.state, SlotState.INVALID) self.assertEqual(slot.tag, 0) # Slot 1 should be SHARED, tag == 1 slot = cache_lines[1] self.assertEqual(slot.state, SlotState.SHARED) self.assertEqual(slot.tag, 1) # Slot 2 should be SHARED, tag == 2 slot = cache_lines[2] self.assertEqual(slot.state, SlotState.SHARED) self.assertEqual(slot.tag, 2) # Slot 3 should be INVALID, tag == 3 slot = cache_lines[3] self.assertEqual(slot.state, SlotState.INVALID) self.assertEqual(slot.tag, 3) # Slot 4 should be INVALID, tag == None (not accessed) slot = cache_lines[4] self.assertEqual(slot.state, SlotState.INVALID) self.assertEqual(slot.tag, None) # Slot 5 should be INVALID, tag == None (not accessed) slot = cache_lines[5] self.assertEqual(slot.state, SlotState.INVALID) self.assertEqual(slot.tag, None) def test_miss_tracking(self): with capture_output(): self.simulation.simulate("test_traces/miss_rate_trace.out") # Check processor 0. stats = self.simulation.tracker.get_cache_stats(0) self.assertEqual(stats["accesses"], 20) self.assertEqual(stats["misses"], 8) self.assertEqual(stats["read_misses"], 3) self.assertEqual(stats["write_misses"], 5) # Check processor 1. stats = self.simulation.tracker.get_cache_stats(1) self.assertEqual(stats["accesses"], 1) self.assertEqual(stats["misses"], 1) self.assertEqual(stats["read_misses"], 1) def test_coherence_miss_tracking(self): with capture_output(): self.simulation.simulate( "test_traces/coherence_miss_rate_trace.out") # Check processor 0. stats = self.simulation.tracker.get_cache_stats(0) self.assertEqual(stats["misses"], 2) self.assertEqual(stats["coherence_misses"], 0) # Check processor 1. stats = self.simulation.tracker.get_cache_stats(1) self.assertEqual(stats["misses"], 4) self.assertEqual(stats["coherence_misses"], 1) # Check processor 2. stats = self.simulation.tracker.get_cache_stats(2) self.assertEqual(stats["misses"], 5) self.assertEqual(stats["coherence_misses"], 4) def test_address_tracking(self): with capture_output(): self.simulation.simulate("test_traces/address_trace.out") # Get the statistics. stats = self.simulation.tracker.get_general_stats() # Check that the addresses are being tracked correctly. self.assertAlmostEqual(stats["addresses"], 8) self.assertAlmostEqual(stats["addressed_by_one_processor"], 2) self.assertAlmostEqual(stats["addressed_by_two_processors"], 2) self.assertAlmostEqual(stats["addressed_by_more_than_two_processors"], 4)
class CachingTest(unittest.TestCase): """Tests for the caching module.""" # Default configuration. Individual tests may override. _NUMBER_OF_PROCESSORS = 4 _NUMBER_OF_CACHE_LINES = 128 _SIZE_OF_CACHE_LINE = 4 def setUp(self): self.fake_bus = FakeBus() self.fake_tracker = FakeTracker() self.default_cache = Cache( 0, # Cache id. CachingTest._NUMBER_OF_CACHE_LINES, CachingTest._SIZE_OF_CACHE_LINE, self.fake_bus, self.fake_tracker, debug_mode=False) # Simulations for the test_trace tests. self.sc_simulation = SimulationEnvironment( CachingTest._NUMBER_OF_PROCESSORS, CachingTest._NUMBER_OF_CACHE_LINES, CachingTest._SIZE_OF_CACHE_LINE, "SC", debug_mode=True, # Turn on to force consistency checks. ) self.tso_simulation = SimulationEnvironment( CachingTest._NUMBER_OF_PROCESSORS, CachingTest._NUMBER_OF_CACHE_LINES, CachingTest._SIZE_OF_CACHE_LINE, "TSO", debug_mode=True, # Turn on to force consistency checks. write_buffer_size=32, retire_at_count=1) def test_initialization(self): """Tests that a Cache is initialized properly.""" # First, the default cache. 128 lines maps to 7 slot bits, 4 words per line # maps to 2 offset bits. self.assertEqual(self.default_cache.cache_id, 0) self.assertEqual(self.default_cache.slot_bits, 7) self.assertEqual(self.default_cache.offset_bits, 2) self.assertEqual(self.default_cache.bus, self.fake_bus) self.assertEqual(self.default_cache.tracker, self.fake_tracker) # A different sized cache. 512 lines ==> 9 slot bits, 1 word per line ==> 0 # offset bits. cache = Cache(1, 512, 1, self.fake_bus, self.fake_tracker, debug_mode=False) self.assertEqual(cache.cache_id, 1) self.assertEqual(cache.slot_bits, 9) self.assertEqual(cache.offset_bits, 0) # One more. 32 lines ==> 5 slot bits, 32 word per line ==> 5 offset bits. cache = Cache(2, 32, 32, self.fake_bus, self.fake_tracker, debug_mode=False) self.assertEqual(cache.cache_id, 2) self.assertEqual(cache.slot_bits, 5) self.assertEqual(cache.offset_bits, 5) # Finally, test the error-throwing cases: non-powers of two. with self.assertRaises(ValueError): Cache(2, 15, 32, self.fake_bus, self.fake_tracker, debug_mode=False) with self.assertRaises(ValueError): Cache(2, 32, 15, self.fake_bus, self.fake_tracker, debug_mode=False) with self.assertRaises(ValueError): Cache(2, -2, 32, self.fake_bus, self.fake_tracker, debug_mode=False) with self.assertRaises(ValueError): Cache(2, 32, -2, self.fake_bus, self.fake_tracker, debug_mode=False) def test_address_placement(self): """Tests that addresses are mapped to the correct slot. Note that this overlaps with testing actual MSI protocol, but that is not the aim here: only the actual breakdown of the address is of interest. The check for SHARED is only to make sure that the line is actually in the cache.""" # First test the default setup: 128 lines, 4 words per line. # Address 5: slot 1, tag 0. self.default_cache.read(5) line = self.default_cache.get_cache_line(1) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 0) # Address 522: slot 2, tag 1. self.default_cache.read(522) line = self.default_cache.get_cache_line(2) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 1) # Address 523: still slot 2, tag 1. self.default_cache.read(523) line = self.default_cache.get_cache_line(2) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 1) # Address 2571: also slot 2, but tag 5. self.default_cache.read(2571) line = self.default_cache.get_cache_line(2) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 5) # Now test another cache: 16 lines, 8 words per line. cache = Cache(1, 16, 8, self.fake_bus, self.fake_tracker, debug_mode=False) # Address 5: slot 0, tag 0. cache.read(5) line = cache.get_cache_line(0) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 0) # Address 522: slot 1, tag 4. cache.read(522) line = cache.get_cache_line(1) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 4) # Address 527: still slot 1, tag 4. cache.read(527) line = cache.get_cache_line(1) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 4) # Address 528: slot 2, still tag 4. cache.read(528) line = cache.get_cache_line(2) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 4) # Address 2571: also slot 1, but tag 20. cache.read(2571) line = cache.get_cache_line(1) self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.tag, 20) def test_local_accesses(self): """Tests that local accesses adhere to the MSI protocol.""" # State transitions should be: # INVALID + read ==> read_miss + SHARED. # INVALID + write ==> write_miss + MODIFIED. # SHARED + read ==> read_hit + SHARED. # SHARED + write ==> write_miss + MODIFIED. # MODIFIED + read ==> read_hit + MODIFIED. # MODIFIED + write ==> write_hit + MODIFIED. # INVALID + read. hit = self.default_cache.read(10) self.assertFalse(hit) line = self.default_cache.get_cache_line(2) # Address 10 ==> slot 2. self.assertEqual(line.state, SlotState.SHARED) self.assertFalse(line.written_to) # INVALID + write. hit = self.default_cache.write(20) self.assertFalse(hit) line = self.default_cache.get_cache_line(5) # Address 20 ==> slot 5. self.assertEqual(line.state, SlotState.MODIFIED) self.assertTrue(line.written_to) # SHARED + read. self.default_cache.read(30) # Prime the cache. hit = self.default_cache.read(31) self.assertTrue(hit) line = self.default_cache.get_cache_line(7) # Address 31 ==> slot 7. self.assertEqual(line.state, SlotState.SHARED) self.assertFalse(line.written_to) # SHARED + write. self.default_cache.read(40) # Prime the cache. hit = self.default_cache.write(41) self.assertFalse(hit) line = self.default_cache.get_cache_line(10) # Address 41 ==> slot 10. self.assertEqual(line.state, SlotState.MODIFIED) self.assertTrue(line.written_to) # MODIFIED + read self.default_cache.write(50) # Prime the cache. hit = self.default_cache.read(51) self.assertTrue(hit) line = self.default_cache.get_cache_line(12) # Address 51 ==> slot 12. self.assertEqual(line.state, SlotState.MODIFIED) self.assertTrue(line.written_to) # MODIFIED + write self.default_cache.write(60) # Prime the cache. hit = self.default_cache.write(61) self.assertTrue(hit) line = self.default_cache.get_cache_line(15) # Address 61 ==> slot 15. self.assertEqual(line.state, SlotState.MODIFIED) self.assertTrue(line.written_to) # Mismatching tags in any non-INVALID state should be equivalent to INVALID. # SHARED + read. self.default_cache.read(30) # Prime the cache. hit = self.default_cache.read(2590) # Access a different tag. self.assertFalse(hit) line = self.default_cache.get_cache_line(7) # Address 30|2590 ==> slot 7. self.assertFalse(line.written_to) # SHARED + write. self.default_cache.read(40) # Prime the cache. hit = self.default_cache.write(10280) # Access a different tag. self.assertFalse(hit) line = self.default_cache.get_cache_line(10) # Address 40|10280 ==> slot 10. self.assertTrue(line.written_to) # MODIFIED + read self.default_cache.write(50) # Prime the cache. hit = self.default_cache.read(5682) # Access a different tag. self.assertFalse(hit) line = self.default_cache.get_cache_line(12) # Address 50|5682 ==> slot 12. self.assertFalse(line.written_to) # 5682 has only been read. # MODIFIED + write self.default_cache.write(60) # Prime the cache. hit = self.default_cache.write(1596) # Access a different tag. self.assertFalse(hit) line = self.default_cache.get_cache_line(15) # Address 60|1596 ==> slot 12. self.assertTrue(line.written_to) def test_remote_accesses(self): """Tests that remote accesses adhere to the MSI protocol.""" # State transitions should be: # INVALID + remote read miss ==> INVALID. # INVALID + remote write miss ==> INVALID. # SHARED + remote read miss ==> SHARED. # SHARED + remote write miss ==> INVALID. # MODIFIED + remote read miss ==> SHARED. # MODIFIED + remote write miss ==> INVALID. # INVALID + remote read miss. self.default_cache.notify_read_miss(10) line = self.default_cache.get_cache_line(2) # Address 10 ==> slot 2. self.assertEqual(line.state, SlotState.INVALID) self.assertEqual(line.previous_state, None) # INVALID + remote write miss. self.default_cache.notify_write_miss(20) line = self.default_cache.get_cache_line(5) # Address 20 ==> slot 5. self.assertEqual(line.state, SlotState.INVALID) self.assertEqual(line.previous_state, None) # SHARED + remote read miss. self.default_cache.read(30) # Prime the cache. self.default_cache.notify_read_miss(31) line = self.default_cache.get_cache_line(7) # Address 31 ==> slot 7. self.assertEqual(line.state, SlotState.SHARED) # The previous_state variable tracks the previous state for coherence-caused # changes, so won't have changed here. self.assertEqual(line.previous_state, SlotState.INVALID) # SHARED + remote write miss. self.default_cache.read(40) # Prime the cache. self.default_cache.notify_write_miss(41) line = self.default_cache.get_cache_line(10) # Address 41 ==> slot 10. self.assertEqual(line.state, SlotState.INVALID) self.assertEqual(line.previous_state, SlotState.SHARED) # MODIFIED + remote read miss. self.default_cache.write(50) # Prime the cache. self.default_cache.notify_read_miss(51) line = self.default_cache.get_cache_line(12) # Address 51 ==> slot 12. self.assertEqual(line.state, SlotState.SHARED) self.assertEqual(line.previous_state, SlotState.MODIFIED) # MODIFIED + remote write miss. self.default_cache.write(60) # Prime the cache. self.default_cache.notify_write_miss(61) line = self.default_cache.get_cache_line(15) # Address 61 ==> slot 15. self.assertEqual(line.state, SlotState.INVALID) self.assertEqual(line.previous_state, SlotState.MODIFIED) # Mismatching tags should not cause a change. # SHARED + write: shouldn't go to INVALID. self.default_cache.read(30) # Prime the cache. self.default_cache.notify_write_miss(2590) # Write miss for different tag. line = self.default_cache.get_cache_line(7) # Address 30|2590 ==> slot 7. self.assertEqual(line.state, SlotState.SHARED) # MODIFIED + read: shouldn't go to SHARED. self.default_cache.write(40) # Prime the cache. self.default_cache.notify_read_miss(10280) # Read miss for different tag. line = self.default_cache.get_cache_line(10) # Address 40|10280 ==> slot 10. self.assertEqual(line.state, SlotState.MODIFIED) def test_write_tracking(self): """Tests that writes to a cache line are correctly tracked.""" # Basic case: line not written to. self.default_cache.read(10) self.default_cache.read(11) line = self.default_cache.get_cache_line(2) # Address 10 ==> slot 2. self.assertFalse(line.written_to) # Basic case: line written to. self.default_cache.read(20) self.default_cache.write(21) self.default_cache.read(21) line = self.default_cache.get_cache_line(5) # Address 20 ==> slot 5. self.assertTrue(line.written_to) # Basic case: line written to, then flushed. self.default_cache.read(30) self.default_cache.write(31) self.default_cache.read(543) # Cache slot flushed. line = self.default_cache.get_cache_line(7) # Address 30|543 ==> slot 7. self.assertFalse(line.written_to) # Basic case: line written to, then flushed, then re-filled. self.default_cache.read(40) self.default_cache.write(41) self.default_cache.read(553) # Cache slot flushed. self.default_cache.read(40) # Re-filled. line = self.default_cache.get_cache_line(10) # Address 50|553 ==> slot 10. self.assertFalse(line.written_to) # More complex case. Line written to, then flushed by external processor. self.default_cache.write(50) self.default_cache.notify_write_miss(51) # Flush. line = self.default_cache.get_cache_line(12) # Address 50 ==> slot 12 self.assertTrue(line.written_to) # More complex case. Line written to, then set to SHARED by external processor. self.default_cache.write(60) self.default_cache.notify_read_miss(61) # Set to SHARED. line = self.default_cache.get_cache_line(15) # Address 60 ==> slot 15 self.assertTrue(line.written_to) def test_sc_read_latency(self): """Tests the measurements of read-latency in an SC cache.""" # Cache with 16 lines, 8 words/line. cache = Cache(1, 16, 8, self.fake_bus, self.fake_tracker, False) # Test object in cache. cache.read(1) # Prime cache. cache.latency = 0 cache.read(1) self.assertEqual(cache.latency, 2) cache.latency = 0 # Test line returned by other processor. cache.read(9) self.assertEqual(cache.latency, 22) cache.latency = 0 # Test line returned by main memory. # Duck-punch the fake bus to claim that it read from memory. # (Don't you love Python? Such abuse! :D) old_read_miss = FakeBus.read_miss def new_read_miss(self, cache_id, address): return True FakeBus.read_miss = new_read_miss cache.read(18) self.assertEqual(cache.latency, 222) FakeBus.read_miss = old_read_miss def test_tso_read_latency(self): """Tests the measurements of read-latency in a TSO cache.""" # Cache with 16 lines, 8 words/line, 4-write buffer with retire-at-2. cache = TSOCache(1, 16, 8, self.fake_bus, self.fake_tracker, 4, 2, False) # First test that write buffers are snooped correctly. cache.write(5) cache.read(5) self.assertEqual(cache.latency, 1) cache.latency = 0 # Test value that is in the L1 cache. cache.read(4) # Prime the cache. cache.latency = 0 cache.read(4) self.assertEqual(cache.latency, 3) cache.latency = 0 # Test value returned by other processor. cache.read(19) self.assertEqual(cache.latency, 23) cache.latency = 0 # Test value returned by main memory. # Duck-punch the fake bus to claim that it read from memory. # (Don't you love Python? Such abuse! :D) old_read_miss = FakeBus.read_miss def new_read_miss(self, cache_id, address): return True FakeBus.read_miss = new_read_miss cache.read(26) self.assertEqual(cache.latency, 223) # Restore the FakeBus class. FakeBus.read_miss = old_read_miss def test_sc_write_latency(self): """Tests the measurements of write-latency in an SC cache. All writes in SC take 222 cycles, due to write-through cache.""" # Cache with 16 lines, 8 words/line. cache = Cache(1, 16, 8, self.fake_bus, self.fake_tracker, False) # Test writing to something in cache. cache.write(5) # Prime cache. cache.latency = 0 cache.write(5) self.assertEqual(cache.latency, 222) cache.latency = 0 # Test writing to something that another processor can return. cache.write(9) self.assertEqual(cache.latency, 222) cache.latency = 0 # Test value returned by main memory. # Duck-punch the fake bus to claim that it read from memory. # (Don't you love Python? Such abuse! :D) old_read_miss = FakeBus.read_miss def new_read_miss(self, cache_id, address): return True FakeBus.read_miss = new_read_miss cache.write(20) self.assertEqual(cache.latency, 222) # Restore the FakeBus class. FakeBus.read_miss = old_read_miss def test_tso_write_latency(self): """Tests the measurements of write-latency in a TSO cache. Write latency is complicated in TSO: writes normally do not add to the cache latency, unless they are involved in a write buffer drain. Additionally, any write executing when a write buffer drain happens contributes the remainder of its cycles to the latency.""" # Cache with 16 lines, 8 words/line, 4-write buffer with retire-at-2. cache = TSOCache(1, 16, 8, self.fake_bus, self.fake_tracker, 4, 2, False) # Single write: should be buffered. cache.write(5) self.assertEqual(cache.latency, 0) # A second write: should trip retire at N, but no effect on latency. cache.write(10) self.assertEqual(cache.latency, 0) self.assertEqual(len(cache.write_buffer), 1) # Force a drain. cache.write(15) # 2 writes in buffer cache.write(20) # 3 writes in buffer cache.write(25) # 4 writes in buffer; buffer should be full. cache.write(30) # Should force a drain. # Latency will be 4 writes in buffer, plus the one that was processing. self.assertEqual(cache.latency, 222*5) self.assertEqual(len(cache.write_buffer), 1) def test_tso_check_write_buffer(self): """Tests that the check-buffer logic in the TSO cache is correct.""" # Cache with 16 lines, 8 words/line, 4-write buffer with retire-at-2. cache = TSOCache(1, 16, 8, self.fake_bus, self.fake_tracker, 4, 2, False) # First, check that the method does nothing if a write is still processing. cache.write_finishes_at = 15 # Fake a write in progress. cache.write_buffer = [10, 5, 20] # Fake some writes. cache._check_write_buffer() self.assertEqual(cache.write_finishes_at, 15) self.assertEqual(cache.latency, 0) self.assertEqual(cache.write_buffer, [10, 5, 20]) # Check that if a write is not in process but there are less than N (here, # 2) writes in the buffer, it still does nothing. cache.write_finishes_at = None cache.write_buffer = [10] cache._check_write_buffer() self.assertEqual(cache.write_finishes_at, None) self.assertEqual(cache.latency, 0) self.assertEqual(cache.write_buffer, [10]) # Check that if a write is finished and there are less than N (here, 2) # writes in the buffer, the write is cleared but thats it. cache.write_finishes_at = 10 cache.latency = 15 cache.write_buffer = [10] cache._check_write_buffer() self.assertEqual(cache.write_finishes_at, None) self.assertEqual(cache.latency, 15) self.assertEqual(cache.write_buffer, [10]) # Check that if a write is finished and there are N writes in the buffer, # another write is retired, from the correct end of the line. cache.write_finishes_at = 10 cache.latency = 15 cache.write_buffer = [10, 5] cache._check_write_buffer() self.assertEqual(cache.write_finishes_at, 237) self.assertEqual(cache.latency, 15) self.assertEqual(cache.write_buffer, [5]) # Check that if a write is not in progress and there are N writes in the # buffer, another write is retired, from the correct end of the line. cache.write_finishes_at = None cache.latency = 25 cache.write_buffer = [5, 10] cache._check_write_buffer() self.assertEqual(cache.write_finishes_at, 247) self.assertEqual(cache.latency, 25) self.assertEqual(cache.write_buffer, [10]) def test_tso_drain_buffer(self): """Tests that the drain buffer logic in the TSO cache is correct.""" # Cache with 16 lines, 8 words/line, 4-write buffer with retire-at-2. cache = TSOCache(1, 16, 8, self.fake_bus, self.fake_tracker, 4, 2, False) # Check that draining a cache always clears it completely. cache.write_buffer = [1] cache._drain_write_buffer() self.assertEqual(cache.write_buffer, []) cache.write_buffer = [1, 2] cache._drain_write_buffer() self.assertEqual(cache.write_buffer, []) cache.write_buffer = [3, 4, 5, 6, 7] cache._drain_write_buffer() self.assertEqual(cache.write_buffer, []) cache.write_buffer = [] cache._drain_write_buffer() self.assertEqual(cache.write_buffer, []) # Check that when the buffer is drained, any ongoing writes are counted as latency. cache.write_buffer = [] cache.latency = 20 cache.write_finishes_at = 126 cache._drain_write_buffer() self.assertEqual(cache.latency, 126) self.assertEqual(cache.write_finishes_at, None) cache.write_buffer = [10, 20] cache.latency = 20 cache.write_finishes_at = 126 cache._drain_write_buffer() self.assertEqual(cache.latency, 126 + (2 * 222)) self.assertEqual(cache.write_finishes_at, None) # Check that when the buffer is drained, all writes are counted. cache.write_buffer = [10, 20, 30, 40] cache.latency = 0 cache.write_finishes_at = None cache._drain_write_buffer() self.assertEqual(cache.latency, 4 * 222) self.assertEqual(cache.write_finishes_at, None) # Check that when drained, writes execute in the correct order. cache.write_buffer = [10, 138] # Same slot (1), different tag (0 and 1). cache.latency = 0 cache.write_finishes_at = None cache._drain_write_buffer() self.assertEqual(cache.latency, 2 * 222) self.assertEqual(cache.get_cache_line(1).tag, 1) self.assertEqual(cache.get_cache_line(1).state, SlotState.MODIFIED) def test_tso_post_program_drain(self): """Test the post-program drain of the write buffer.""" # Cache with 16 lines, 8 words/line, 4-write buffer with retire-at-2. cache = TSOCache(1, 16, 8, self.fake_bus, self.fake_tracker, 4, 2, False) # Test that nothing changes if theres nothing in the write buffer. cache.latency = 500 cache.notify_finished() self.assertEqual(cache.latency, 500) self.assertEqual(cache.write_buffer, []) self.assertEqual(cache.write_finishes_at, None) # Test that the write buffer is properly cleared if there is something in it. cache.latency = 500 cache.write_buffer = [10, 20] cache.write_finishes_at = 505 cache.notify_finished() self.assertEqual(cache.latency, 505 + (2 * 222)) self.assertEqual(cache.write_buffer, []) self.assertEqual(cache.write_finishes_at, None) def test_sc_read_latency_from_trace(self): """Tests that SC read latency is correctly calculated from a trace. Basically worse than the unittests, but apparently we must provide test traces...""" with capture_output(): self.sc_simulation.simulate("test_traces/sc_read_trace.out") # Get the statistics. stats = self.sc_simulation.tracker.get_general_stats() self.assertEqual(stats["max_latency"], 246) self.assertEqual(stats["max_latency_cache"], 0) def test_sc_write_latency_from_trace(self): """Tests that SC write latency is correctly calculated from a trace. Basically worse than the unittests, but apparently we must provide test traces...""" with capture_output(): self.sc_simulation.simulate("test_traces/sc_write_trace.out") # Get the statistics. stats = self.sc_simulation.tracker.get_general_stats() self.assertEqual(stats["max_latency"], 666) self.assertEqual(stats["max_latency_cache"], 0) def test_tso_read_latency_from_trace(self): """Tests that TSO read latency is correctly calculated from a trace. Basically worse than the unittests, but apparently we must provide test traces...""" with capture_output(): self.tso_simulation.simulate("test_traces/tso_read_trace.out") # Get the statistics. stats = self.tso_simulation.tracker.get_general_stats() self.assertEqual(stats["max_latency"], 693) self.assertEqual(stats["max_latency_cache"], 0) def test_tso_write_latency_from_trace(self): """Tests that TSO write latency is correctly calculated from a trace. Basically worse than the unittests, but apparently we must provide test traces...""" with capture_output(): self.tso_simulation.simulate("test_traces/tso_write_trace.out") # Get the statistics. stats = self.tso_simulation.tracker.get_general_stats() self.assertEqual(stats["max_latency"], 889) self.assertEqual(stats["max_latency_cache"], 0)