From a2fece68916c3deab79c7338697bfdb5d33c004e Mon Sep 17 00:00:00 2001 From: Nahome Bete Date: Wed, 25 May 2022 00:51:51 +0000 Subject: [PATCH] i#4842 drcachesim: Add unit tests for FIFO cache simulator (#5454) Adds get_next_way_to_replace() helper method that just returns the victim way without a side effect on the cache state. This makes unit testing the cache replacement policies convenient and avoids unexpected behavior during testing. Adds cache_policy_test_t class template for testing multiple cache replacement policies. Adds get_block_index() method that returns the block index for a given address. Issue: #4842, #5456 --- clients/drcachesim/simulator/cache_fifo.cpp | 27 ++- clients/drcachesim/simulator/cache_fifo.h | 6 +- clients/drcachesim/simulator/cache_lru.cpp | 24 ++- clients/drcachesim/simulator/cache_lru.h | 6 +- .../drcachesim/simulator/caching_device.cpp | 11 +- clients/drcachesim/simulator/caching_device.h | 15 +- .../cache_replacement_policy_unit_test.cpp | 195 ++++++++++++++---- 7 files changed, 223 insertions(+), 61 deletions(-) diff --git a/clients/drcachesim/simulator/cache_fifo.cpp b/clients/drcachesim/simulator/cache_fifo.cpp index 6c3a4d55abd..0f31bd02675 100644 --- a/clients/drcachesim/simulator/cache_fifo.cpp +++ b/clients/drcachesim/simulator/cache_fifo.cpp @@ -70,17 +70,32 @@ cache_fifo_t::access_update(int block_idx, int way) return; } +// This method replaces the current victim way and returns the next victim way +// to be replaced. As opposed to get_next_way_to_replace() which just returns the +// next way to be replaced without updating the cache state, replace_which_way() +// updates the cache state. int cache_fifo_t::replace_which_way(int block_idx) { - // We replace the block whose counter is 1. + int victim_way = get_next_way_to_replace(block_idx); + if (victim_way == -1) + return -1; + // clear the counter of the victim block + get_caching_device_block(block_idx, victim_way).counter_ = 0; + // set the next block as victim + get_caching_device_block(block_idx, (victim_way + 1) & (associativity_ - 1)) + .counter_ = 1; + return victim_way; +} + +// This method just returns the next way to be replaced without actually +// replacing it, hence doesn't have a side-effect. +int +cache_fifo_t::get_next_way_to_replace(const int block_idx) const +{ for (int i = 0; i < associativity_; i++) { + // We return the block whose counter is 1. if (get_caching_device_block(block_idx, i).counter_ == 1) { - // clear the counter of the victim block - get_caching_device_block(block_idx, i).counter_ = 0; - // set the next block as victim - get_caching_device_block(block_idx, (i + 1) & (associativity_ - 1)).counter_ = - 1; return i; } } diff --git a/clients/drcachesim/simulator/cache_fifo.h b/clients/drcachesim/simulator/cache_fifo.h index db914927eae..1defc76e95e 100644 --- a/clients/drcachesim/simulator/cache_fifo.h +++ b/clients/drcachesim/simulator/cache_fifo.h @@ -49,9 +49,11 @@ class cache_fifo_t : public cache_t { protected: void - access_update(int line_idx, int way) override; + access_update(int block_idx, int way) override; int - replace_which_way(int line_idx) override; + replace_which_way(int block_idx) override; + int + get_next_way_to_replace(const int block_idx) const override; }; #endif /* _CACHE_FIFO_H_ */ diff --git a/clients/drcachesim/simulator/cache_lru.cpp b/clients/drcachesim/simulator/cache_lru.cpp index 23ea8a71704..d9940dad151 100644 --- a/clients/drcachesim/simulator/cache_lru.cpp +++ b/clients/drcachesim/simulator/cache_lru.cpp @@ -63,34 +63,40 @@ cache_lru_t::init(int associativity, int block_size, int total_size, } void -cache_lru_t::access_update(int line_idx, int way) +cache_lru_t::access_update(int block_idx, int way) { - int cnt = get_caching_device_block(line_idx, way).counter_; + int cnt = get_caching_device_block(block_idx, way).counter_; // Optimization: return early if it is a repeated access. if (cnt == 0) return; // We inc all the counters that are not larger than cnt for LRU. for (int i = 0; i < associativity_; ++i) { - if (i != way && get_caching_device_block(line_idx, i).counter_ <= cnt) - get_caching_device_block(line_idx, i).counter_++; + if (i != way && get_caching_device_block(block_idx, i).counter_ <= cnt) + get_caching_device_block(block_idx, i).counter_++; } // Clear the counter for LRU. - get_caching_device_block(line_idx, way).counter_ = 0; + get_caching_device_block(block_idx, way).counter_ = 0; } int -cache_lru_t::replace_which_way(int line_idx) +cache_lru_t::replace_which_way(int block_idx) +{ + return get_next_way_to_replace(block_idx); +} + +int +cache_lru_t::get_next_way_to_replace(int block_idx) const { // We implement LRU by picking the slot with the largest counter value. int max_counter = 0; int max_way = 0; for (int way = 0; way < associativity_; ++way) { - if (get_caching_device_block(line_idx, way).tag_ == TAG_INVALID) { + if (get_caching_device_block(block_idx, way).tag_ == TAG_INVALID) { max_way = way; break; } - if (get_caching_device_block(line_idx, way).counter_ > max_counter) { - max_counter = get_caching_device_block(line_idx, way).counter_; + if (get_caching_device_block(block_idx, way).counter_ > max_counter) { + max_counter = get_caching_device_block(block_idx, way).counter_; max_way = way; } } diff --git a/clients/drcachesim/simulator/cache_lru.h b/clients/drcachesim/simulator/cache_lru.h index e0bf6c5b715..88e84d08a96 100644 --- a/clients/drcachesim/simulator/cache_lru.h +++ b/clients/drcachesim/simulator/cache_lru.h @@ -49,9 +49,11 @@ class cache_lru_t : public cache_t { protected: void - access_update(int line_idx, int way) override; + access_update(int block_idx, int way) override; int - replace_which_way(int line_idx) override; + replace_which_way(int block_idx) override; + int + get_next_way_to_replace(const int block_idx) const override; }; #endif /* _CACHE_LRU_H_ */ diff --git a/clients/drcachesim/simulator/caching_device.cpp b/clients/drcachesim/simulator/caching_device.cpp index 8c2b5ff81f0..f9b5e2cf877 100644 --- a/clients/drcachesim/simulator/caching_device.cpp +++ b/clients/drcachesim/simulator/caching_device.cpp @@ -256,6 +256,15 @@ caching_device_t::access_update(int block_idx, int way) int caching_device_t::replace_which_way(int block_idx) +{ + int min_way = get_next_way_to_replace(block_idx); + // Clear the counter for LFU. + get_caching_device_block(block_idx, min_way).counter_ = 0; + return min_way; +} + +int +caching_device_t::get_next_way_to_replace(const int block_idx) const { // The base caching device class only implements LFU. // A subclass can override this and access_update() to implement @@ -272,8 +281,6 @@ caching_device_t::replace_which_way(int block_idx) min_way = way; } } - // Clear the counter for LFU. - get_caching_device_block(block_idx, min_way).counter_ = 0; return min_way; } diff --git a/clients/drcachesim/simulator/caching_device.h b/clients/drcachesim/simulator/caching_device.h index 93325aa91b7..be8f44d8925 100644 --- a/clients/drcachesim/simulator/caching_device.h +++ b/clients/drcachesim/simulator/caching_device.h @@ -115,28 +115,37 @@ class caching_device_t { } use_tag2block_table_ = use_hashtable; } + int + get_block_index(const addr_t addr) const + { + addr_t tag = compute_tag(addr); + int block_idx = compute_block_idx(tag); + return block_idx; + } protected: virtual void access_update(int block_idx, int way); virtual int replace_which_way(int block_idx); + virtual int + get_next_way_to_replace(const int block_idx) const; virtual void record_access_stats(const memref_t &memref, bool hit, caching_device_block_t *cache_block); inline addr_t - compute_tag(addr_t addr) + compute_tag(addr_t addr) const { return addr >> block_size_bits_; } inline int - compute_block_idx(addr_t tag) + compute_block_idx(addr_t tag) const { return (tag & blocks_per_set_mask_) << assoc_bits_; } inline caching_device_block_t & - get_caching_device_block(int block_idx, int way) + get_caching_device_block(int block_idx, int way) const { return *(blocks_[block_idx + way]); } diff --git a/clients/drcachesim/tests/cache_replacement_policy_unit_test.cpp b/clients/drcachesim/tests/cache_replacement_policy_unit_test.cpp index f21b9b63d4f..8bd8d090066 100644 --- a/clients/drcachesim/tests/cache_replacement_policy_unit_test.cpp +++ b/clients/drcachesim/tests/cache_replacement_policy_unit_test.cpp @@ -35,79 +35,200 @@ #undef NDEBUG #include #include "cache_replacement_policy_unit_test.h" +#include "simulator/cache_fifo.h" #include "simulator/cache_lru.h" -class cache_lru_test_t : public cache_lru_t { +template class cache_policy_test_t : public T { + int associativity_; + int line_size_; + int total_size_; + public: + cache_policy_test_t(int associativity, int line_size, int total_size) + { + associativity_ = associativity; + line_size_ = line_size; + total_size_ = total_size; + } void - initialize_cache(int associativity, int line_size, int total_size) + initialize_cache() { - caching_device_stats_t *stats = new cache_stats_t(line_size, "", true); - if (!init(associativity, line_size, total_size, nullptr, stats, nullptr)) { - std::cerr << "LRU cache failed to initialize\n"; + caching_device_stats_t *stats = new cache_stats_t(line_size_, "", true); + if (!this->init(associativity_, line_size_, total_size_, nullptr, stats, + nullptr)) { + std::cerr << "FIFO cache failed to initialize\n"; exit(1); } } - int - get_block_index(const addr_t addr) - { - addr_t tag = compute_tag(addr); - int block_idx = compute_block_idx(tag); - return block_idx; - } - void - access_and_check_lru(const addr_t addr, - const int expected_replacement_way_after_access) + access_and_check_cache(const addr_t addr, + const int expected_replacement_way_after_access) { memref_t ref; ref.data.type = TRACE_TYPE_READ; ref.data.size = 1; ref.data.addr = addr; - request(ref); - assert(replace_which_way(get_block_index(addr)) == + this->request(ref); + assert(this->get_next_way_to_replace(this->get_block_index(addr)) == expected_replacement_way_after_access); } + + bool + tags_are_different(const std::vector &addresses) + { + // Quadratic solution is ok here since we expect only very short vectors. + for (int i = 0; i < addresses.size(); ++i) { + for (int j = 0; j < addresses.size(); ++j) { + if (i != j) { // Skip comparison if same element. + if (this->compute_tag(addresses[i]) == + this->compute_tag(addresses[j])) { + return false; + } + } + } + } + return true; + } + + bool + block_indices_are_identical(const std::vector &addresses) + { + for (int i = 1; i < addresses.size(); ++i) { + if (this->get_block_index(addresses[i - 1]) != + this->get_block_index(addresses[i])) { + return false; + } + } + return true; + } }; void unit_test_cache_lru_four_way() { - cache_lru_test_t cache_lru_test; - cache_lru_test.initialize_cache(/*associativity=*/4, /*line_size=*/32, - /*total_size=*/256); + cache_policy_test_t cache_lru_test(/*associativity=*/4, + /*line_size=*/32, + /*total_size=*/256); + cache_lru_test.initialize_cache(); + const addr_t ADDRESS_A = 0; const addr_t ADDRESS_B = 64; const addr_t ADDRESS_C = 128; const addr_t ADDRESS_D = 192; - const addr_t ADDRESS_E = 72; + const addr_t ADDRESS_E = 320; - assert(cache_lru_test.get_block_index(ADDRESS_A) == - cache_lru_test.get_block_index(ADDRESS_B)); - assert(cache_lru_test.get_block_index(ADDRESS_B) == - cache_lru_test.get_block_index(ADDRESS_C)); - assert(cache_lru_test.get_block_index(ADDRESS_C) == - cache_lru_test.get_block_index(ADDRESS_D)); - assert(cache_lru_test.get_block_index(ADDRESS_D) == - cache_lru_test.get_block_index(ADDRESS_E)); + const std::vector address_vector = { ADDRESS_A, ADDRESS_B, ADDRESS_C, + ADDRESS_D, ADDRESS_E }; + assert(cache_lru_test.block_indices_are_identical(address_vector)); + assert(cache_lru_test.tags_are_different(address_vector)); // Access the cache line in the following fashion. This sequence follows the // sequence shown in i#4881. // Lower-case letter shows the least recently used way. - cache_lru_test.access_and_check_lru(ADDRESS_A, 1); // A x X X - cache_lru_test.access_and_check_lru(ADDRESS_B, 2); // A B x X - cache_lru_test.access_and_check_lru(ADDRESS_C, 3); // A B C x - cache_lru_test.access_and_check_lru(ADDRESS_D, 0); // a B C D - cache_lru_test.access_and_check_lru(ADDRESS_A, 1); // A b C D - cache_lru_test.access_and_check_lru(ADDRESS_A, 1); // A b C D - cache_lru_test.access_and_check_lru(ADDRESS_A, 1); // A b C D - cache_lru_test.access_and_check_lru(ADDRESS_E, 2); // A E c D + cache_lru_test.access_and_check_cache(ADDRESS_A, 1); // A x X X + cache_lru_test.access_and_check_cache(ADDRESS_B, 2); // A B x X + cache_lru_test.access_and_check_cache(ADDRESS_C, 3); // A B C x + cache_lru_test.access_and_check_cache(ADDRESS_D, 0); // a B C D + cache_lru_test.access_and_check_cache(ADDRESS_A, 1); // A b C D + cache_lru_test.access_and_check_cache(ADDRESS_A, 1); // A b C D + cache_lru_test.access_and_check_cache(ADDRESS_A, 1); // A b C D + cache_lru_test.access_and_check_cache(ADDRESS_E, 2); // A E c D +} + +void +unit_test_cache_fifo_four_way() +{ + cache_policy_test_t cache_fifo_test(/*associativity=*/4, + /*line_size=*/32, + /*total_size=*/256); + cache_fifo_test.initialize_cache(); + + const addr_t ADDRESS_A = 0; + const addr_t ADDRESS_B = 64; + const addr_t ADDRESS_C = 128; + const addr_t ADDRESS_D = 256; + const addr_t ADDRESS_E = 320; + const addr_t ADDRESS_F = 384; + const addr_t ADDRESS_G = 448; + const addr_t ADDRESS_H = 512; + + const std::vector address_vector = { ADDRESS_A, ADDRESS_B, ADDRESS_C, + ADDRESS_D, ADDRESS_E, ADDRESS_F, + ADDRESS_G, ADDRESS_H }; + assert(cache_fifo_test.block_indices_are_identical(address_vector)); + assert(cache_fifo_test.tags_are_different(address_vector)); + + // Lower-case letter shows the way that is to be replaced after the access. + cache_fifo_test.access_and_check_cache(ADDRESS_A, 1); // A x X X + cache_fifo_test.access_and_check_cache(ADDRESS_B, 2); // A B x X + cache_fifo_test.access_and_check_cache(ADDRESS_C, 3); // A B C x + cache_fifo_test.access_and_check_cache(ADDRESS_D, 0); // a B C D + cache_fifo_test.access_and_check_cache(ADDRESS_A, 0); // a B C D + cache_fifo_test.access_and_check_cache(ADDRESS_A, 0); // a B C D + cache_fifo_test.access_and_check_cache(ADDRESS_A, 0); // a B C D + cache_fifo_test.access_and_check_cache(ADDRESS_E, 1); // E b C D + cache_fifo_test.access_and_check_cache(ADDRESS_F, 2); // E F c D + cache_fifo_test.access_and_check_cache(ADDRESS_F, 2); // E F c D + cache_fifo_test.access_and_check_cache(ADDRESS_G, 3); // E F G d + cache_fifo_test.access_and_check_cache(ADDRESS_G, 3); // E F G d + cache_fifo_test.access_and_check_cache(ADDRESS_H, 0); // e F G H + cache_fifo_test.access_and_check_cache(ADDRESS_A, 1); // A f G H +} + +void +unit_test_cache_fifo_eight_way() +{ + cache_policy_test_t cache_fifo_test(/*associativity=*/8, + /*line_size=*/64, + /*total_size=*/1024); + cache_fifo_test.initialize_cache(); + + const addr_t ADDRESS_A = 0; + const addr_t ADDRESS_B = 128; + const addr_t ADDRESS_C = 256; + const addr_t ADDRESS_D = 384; + const addr_t ADDRESS_E = 512; + const addr_t ADDRESS_F = 640; + const addr_t ADDRESS_G = 768; + const addr_t ADDRESS_H = 896; + const addr_t ADDRESS_I = 1024; + const addr_t ADDRESS_J = 1152; + const addr_t ADDRESS_K = 1280; + const addr_t ADDRESS_L = 1408; + + const std::vector address_vector = { ADDRESS_A, ADDRESS_B, ADDRESS_C, + ADDRESS_D, ADDRESS_E, ADDRESS_F, + ADDRESS_G, ADDRESS_H, ADDRESS_I, + ADDRESS_J, ADDRESS_K, ADDRESS_L }; + assert(cache_fifo_test.block_indices_are_identical(address_vector)); + assert(cache_fifo_test.tags_are_different(address_vector)); + + // Lower-case letter shows the way that is to be replaced after the access + // (aka 'first way'). + cache_fifo_test.access_and_check_cache(ADDRESS_A, 1); // A x X X X X X X + cache_fifo_test.access_and_check_cache(ADDRESS_B, 2); // A B x X X X X X + cache_fifo_test.access_and_check_cache(ADDRESS_C, 3); // A B C x X X X X + cache_fifo_test.access_and_check_cache(ADDRESS_D, 4); // A B C D x X X X + cache_fifo_test.access_and_check_cache(ADDRESS_E, 5); // A B C D E x X X + cache_fifo_test.access_and_check_cache(ADDRESS_F, 6); // A B C D E F x X + cache_fifo_test.access_and_check_cache(ADDRESS_G, 7); // A B C D F F G x + cache_fifo_test.access_and_check_cache(ADDRESS_H, 0); // a B C D E F G H + cache_fifo_test.access_and_check_cache(ADDRESS_E, 0); // a B C D E F G H + cache_fifo_test.access_and_check_cache(ADDRESS_A, 0); // a B C D E F G H + cache_fifo_test.access_and_check_cache(ADDRESS_A, 0); // a B C D E F G H + cache_fifo_test.access_and_check_cache(ADDRESS_I, 1); // I b C D E F G H + cache_fifo_test.access_and_check_cache(ADDRESS_J, 2); // I J c D E F G H + cache_fifo_test.access_and_check_cache(ADDRESS_K, 3); // I J K d E F G H + cache_fifo_test.access_and_check_cache(ADDRESS_L, 4); // I J K L e F G H + cache_fifo_test.access_and_check_cache(ADDRESS_L, 4); // I J K L e F G H } void unit_test_cache_replacement_policy() { unit_test_cache_lru_four_way(); + unit_test_cache_fifo_four_way(); + unit_test_cache_fifo_eight_way(); // XXX i#4842: Add more test sequences. }