From 3bf750f8569b4ddfa6f0b208249dcbe0edde952d Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sat, 3 Mar 2018 19:24:24 -0800 Subject: [PATCH 01/31] Upgrade all dependencies --- requirements.txt | 27 ++++++++++++--------------- tests/test_compliance.py | 4 ++-- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/requirements.txt b/requirements.txt index 63470c3..35e658c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,13 @@ -appdirs==1.4.3 -colorama==0.3.5 -coverage==4.0.3 -flake8==2.5.4 -mando==0.3.3 -mccabe==0.4.0 +colorama==0.3.9 +coverage==4.5.1 +flake8==3.5.0 +flake8-polyfill==1.0.2 +mando==0.6.4 +mccabe==0.6.1 nose==1.3.7 -packaging==16.8 -pep8==1.7.0 -pyflakes==1.0.0 -pypandoc==1.4 -pyparsing==2.2.0 -python-termstyle==0.1.10 -radon==1.2.2 -rednose==0.4.3 -six==1.10.0 +pycodestyle==2.3.1 +pyflakes==1.6.0 +radon==2.2.0 +rednose==1.3.0 +six==1.11.0 +termstyle==0.1.11 diff --git a/tests/test_compliance.py b/tests/test_compliance.py index 2e9ce3e..e71af86 100644 --- a/tests/test_compliance.py +++ b/tests/test_compliance.py @@ -2,14 +2,14 @@ import glob import nose.tools as nose -import pep8 +import pycodestyle import radon.complexity as radon def test_pep8(): file_paths = glob.iglob('*/*.py') for file_path in file_paths: - style_guide = pep8.StyleGuide(quiet=True) + style_guide = pycodestyle.StyleGuide(quiet=True) total_errors = style_guide.input_file(file_path) test_pep8.__doc__ = '{} should comply with PEP 8'.format(file_path) fail_msg = '{} does not comply with PEP 8'.format(file_path) From 0d6c793842a0faca487f0273bab5bdbeafbb7dcd Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 14:14:22 -0800 Subject: [PATCH 02/31] Break up simulator into separate classes The following classes have been created: Cache, BinaryAddress, WordAddress --- .coveragerc | 3 + cachesimulator/bin_addr.py | 81 +++++++++++++++ cachesimulator/cache.py | 82 +++++++++++++++ cachesimulator/simulator.py | 164 +++++------------------------- cachesimulator/table.py | 2 + cachesimulator/word_addr.py | 14 +++ tests/test_simulator_hit.py | 12 ++- tests/test_simulator_refs.py | 12 ++- tests/test_simulator_set_block.py | 27 +++-- tests/test_simulator_utility.py | 40 ++++---- 10 files changed, 253 insertions(+), 184 deletions(-) create mode 100644 cachesimulator/bin_addr.py create mode 100644 cachesimulator/cache.py create mode 100644 cachesimulator/word_addr.py diff --git a/.coveragerc b/.coveragerc index e9d8cd7..81c47ab 100644 --- a/.coveragerc +++ b/.coveragerc @@ -15,4 +15,7 @@ exclude_lines = # Only check coverage for source files include = cachesimulator/simulator.py + cachesimulator/word_addr.py + cachesimulator/bin_addr.py + cachesimulator/cache.py cachesimulator/table.py diff --git a/cachesimulator/bin_addr.py b/cachesimulator/bin_addr.py new file mode 100644 index 0000000..2a73b62 --- /dev/null +++ b/cachesimulator/bin_addr.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + + +class BinaryAddress(object): + + # Retrieves the binary address of a certain length for a base-10 word + # address + def __init__(self, bin_addr=None, word_addr=None, num_addr_bits=0): + + if word_addr is not None: + self.value = bin(word_addr.value)[2:].zfill(num_addr_bits) + else: + self.value = bin_addr + + def __repr__(self): + return repr(self.value) + + def __format__(self, formatstr): + return str.__format__(self.value, formatstr) + + def __hash__(self): + return hash(self.value) + + def __len__(self): + return len(self.value) + + def __getitem__(self, key): + return self.value[key] + + def __eq__(self, other): + return self.value == other + + def __lt__(self, other): + return self.value < other.value + + @classmethod + def prettify(cls, bin_addr, min_bits_per_group): + + mid = len(bin_addr) // 2 + + if mid < min_bits_per_group: + # Return binary string immediately if bisecting the binary string + # produces a substring which is too short + return bin_addr + else: + # Otherwise, bisect binary string and separate halves with a space + left = cls.prettify(bin_addr[:mid], min_bits_per_group) + right = cls.prettify(bin_addr[mid:], min_bits_per_group) + return ' '.join((left, right)) + + # Retrieves the tag used to distinguish cache entries with the same index + def get_tag(self, num_tag_bits): + + end = num_tag_bits + tag = self.value[:end] + if len(tag) != 0: + return tag + else: + return None + + # Retrieves the index used to group blocks in the cache + def get_index(self, num_offset_bits, num_index_bits): + + start = len(self.value) - num_offset_bits - num_index_bits + end = len(self.value) - num_offset_bits + index = self.value[start:end] + if len(index) != 0: + return index + else: + return None + + # Retrieves the word offset used to select a word in the data pointed to by + # the given binary address + def get_offset(self, num_offset_bits): + + start = len(self.value) - num_offset_bits + offset = self.value[start:] + if len(offset) != 0: + return offset + else: + return None diff --git a/cachesimulator/cache.py b/cachesimulator/cache.py new file mode 100644 index 0000000..8066640 --- /dev/null +++ b/cachesimulator/cache.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 + +import copy + +from cachesimulator.word_addr import WordAddress +from cachesimulator.bin_addr import BinaryAddress + + +class Cache(object): + + # Initializes the reference cache with a fixed number of sets + def __init__(self, cache=None, num_sets=None, num_index_bits=None): + + if cache is not None: + self.map = cache + else: + self.map = {} + for i in range(num_sets): + index = BinaryAddress( + word_addr=WordAddress(i), num_addr_bits=num_index_bits) + self.map[index] = [] + + def __eq__(self, other): + return self.map == other + + def __len__(self): + return len(self.map) + + def __getitem__(self, key): + return self.map[key] + + def __contains__(self, value): + return value in self.map + + def __deepcopy__(self, memodict={}): + return copy.deepcopy(self.map) + + def keys(self): + return self.map.keys() + + # Returns True if a block at the given index and tag exists in the cache, + # indicating a hit; returns False otherwise, indicating a miss + def is_hit(self, addr_index, addr_tag): + + # Ensure that indexless fully associative caches are accessed correctly + if addr_index is None: + blocks = self['0'] + elif addr_index in self: + blocks = self[addr_index] + else: + return False + + for block in blocks: + if block['tag'] == addr_tag: + return True + + return False + + # Adds the given entry to the cache at the given index + def set_block(self, recently_used_addrs, replacement_policy, + num_blocks_per_set, addr_index, new_entry): + + # Place all cache entries in a single set if cache is fully associative + if addr_index is None: + blocks = self['0'] + else: + blocks = self[addr_index] + # Replace MRU or LRU entry if number of blocks in set exceeds the limit + if len(blocks) == num_blocks_per_set: + # Iterate through the recently-used entries in reverse order for + # MRU + if replacement_policy == 'mru': + recently_used_addrs = reversed(recently_used_addrs) + # Replace the first matching entry with the entry to add + for recent_index, recent_tag in recently_used_addrs: + for i, block in enumerate(blocks): + if (recent_index == addr_index and + block['tag'] == recent_tag): + blocks[i] = new_entry + return + else: + blocks.append(new_entry) diff --git a/cachesimulator/simulator.py b/cachesimulator/simulator.py index ea124ae..f519b75 100755 --- a/cachesimulator/simulator.py +++ b/cachesimulator/simulator.py @@ -4,8 +4,11 @@ import math import shutil from enum import Enum -from cachesimulator.table import Table +from cachesimulator.word_addr import WordAddress +from cachesimulator.bin_addr import BinaryAddress +from cachesimulator.cache import Cache +from cachesimulator.table import Table # The names of all reference table columns REF_COL_NAMES = ('WordAddr', 'BinAddr', 'Tag', 'Index', 'Offset', 'Hit/Miss') @@ -15,77 +18,6 @@ DEFAULT_TABLE_WIDTH = 80 -# Retrieves the binary address of a certain length for a base-10 word address -def get_bin_addr(word_addr, num_addr_bits=None): - - # Strip the '0b' prefix included in the binary string returned by bin() - bin_addr = bin(word_addr)[2:] - if num_addr_bits is None: - return bin_addr - else: - # Pad binary address with zeroes if too short - bin_addr = bin_addr.zfill(num_addr_bits) - return bin_addr - - -# Formats the given binary address by inserting spaces to improve readability -def prettify_bin_addr(bin_addr, min_bits_per_group): - - mid = len(bin_addr) // 2 - - if mid < min_bits_per_group: - # Return binary string immediately if bisecting the binary string - # produces a substring which is too short - return bin_addr - else: - # Otherwise, bisect binary string and separate halves with a space - left = prettify_bin_addr(bin_addr[:mid], min_bits_per_group) - right = prettify_bin_addr(bin_addr[mid:], min_bits_per_group) - return ' '.join((left, right)) - - -# Retrieves the tag used to distinguish cache entries with the same index -def get_tag(bin_addr, num_tag_bits): - - end = num_tag_bits - tag = bin_addr[:end] - if len(tag) != 0: - return tag - else: - return None - - -# Retrieves the index used to group blocks in the cache -def get_index(bin_addr, num_offset_bits, num_index_bits): - - start = len(bin_addr) - num_offset_bits - num_index_bits - end = len(bin_addr) - num_offset_bits - index = bin_addr[start:end] - if len(index) != 0: - return index - else: - return None - - -# Retrieves the word offset used to select a word in the data pointed to by the -# given binary address -def get_offset(bin_addr, num_offset_bits): - - start = len(bin_addr) - num_offset_bits - offset = bin_addr[start:] - if len(offset) != 0: - return offset - else: - return None - - -# Retrieves all consecutive words for the given word address (including itself) -def get_consecutive_words(word_addr, num_words_per_block): - - offset = word_addr % num_words_per_block - return [(word_addr - offset + i) for i in range(num_words_per_block)] - - # An enum representing the cache status of a reference (i.e. hit or miss) class RefStatus(Enum): @@ -105,54 +37,15 @@ class Reference(object): def __init__(self, word_addr, num_addr_bits, num_offset_bits, num_index_bits, num_tag_bits): - self.word_addr = word_addr - self.bin_addr = get_bin_addr(self.word_addr, num_addr_bits) - self.offset = get_offset(self.bin_addr, num_offset_bits) - self.index = get_index(self.bin_addr, num_offset_bits, num_index_bits) - self.tag = get_tag(self.bin_addr, num_tag_bits) - - -# Returns True if a block at the given index and tag exists in the cache, -# indicating a hit; returns False otherwise, indicating a miss -def is_hit(cache, addr_index, addr_tag): - - # Ensure that indexless fully associative caches are accessed correctly - if addr_index is None: - blocks = cache['0'] - elif addr_index in cache: - blocks = cache[addr_index] - else: - return False - - for block in blocks: - if block['tag'] == addr_tag: - return True - - return False - - -# Adds the given entry to the cache at the given index -def set_block(cache, recently_used_addrs, replacement_policy, - num_blocks_per_set, addr_index, new_entry): - - # Place all cache entries in a single set if cache is fully associative - if addr_index is None: - blocks = cache['0'] - else: - blocks = cache[addr_index] - # Replace MRU or LRU entry if number of blocks in set exceeds the limit - if len(blocks) == num_blocks_per_set: - # Iterate through the recently-used entries in reverse order for MRU - if replacement_policy == 'mru': - recently_used_addrs = reversed(recently_used_addrs) - # Replace the first matching entry with the entry to add - for recent_index, recent_tag in recently_used_addrs: - for i, block in enumerate(blocks): - if recent_index == addr_index and block['tag'] == recent_tag: - blocks[i] = new_entry - return - else: - blocks.append(new_entry) + self.word_addr = WordAddress(word_addr) + self.bin_addr = BinaryAddress( + word_addr=self.word_addr, num_addr_bits=num_addr_bits) + self.offset = self.bin_addr.get_offset(num_offset_bits) + self.index = self.bin_addr.get_index(num_offset_bits, num_index_bits) + self.tag = self.bin_addr.get_tag(num_tag_bits) + + def __repr__(self): + return repr(self.__dict__) # Retrieves a list of address references for use by simulator @@ -170,21 +63,13 @@ def get_addr_refs(word_addrs, num_addr_bits, return refs -# Initializes the reference cache with a fixed number of sets -def create_cache(num_sets, num_index_bits): - - cache = {} - for i in range(num_sets): - index = get_bin_addr(i, num_index_bits) - cache[index] = [] - return cache - - # Simulate the cache by reading the given address references into it def read_refs_into_cache(num_sets, num_blocks_per_set, num_index_bits, num_words_per_block, replacement_policy, refs): - cache = create_cache(num_sets, num_index_bits) + cache = Cache( + num_sets=num_sets, + num_index_bits=num_index_bits) recently_used_addrs = [] ref_statuses = [] @@ -199,7 +84,7 @@ def read_refs_into_cache(num_sets, num_blocks_per_set, num_index_bits, recently_used_addrs.append(addr_id) # Determine the Hit/Miss value for this address to display in the table - if is_hit(cache, ref.index, ref.tag): + if cache.is_hit(ref.index, ref.tag): # Give emphasis to hits in contrast to misses ref_status = RefStatus.hit else: @@ -207,11 +92,10 @@ def read_refs_into_cache(num_sets, num_blocks_per_set, num_index_bits, # Create entry dictionary containing tag and data for this address entry = { 'tag': ref.tag, - 'data': get_consecutive_words( - ref.word_addr, num_words_per_block) + 'data': ref.word_addr.get_consecutive_words( + num_words_per_block) } - set_block( - cache=cache, + cache.set_block( recently_used_addrs=recently_used_addrs, replacement_policy=replacement_policy, num_blocks_per_set=num_blocks_per_set, @@ -250,10 +134,10 @@ def display_addr_refs(refs, ref_statuses, table_width): # Display data for each address as a row in the table table.rows.append(( ref.word_addr, - prettify_bin_addr(ref.bin_addr, MIN_BITS_PER_GROUP), - prettify_bin_addr(ref_tag, MIN_BITS_PER_GROUP), - prettify_bin_addr(ref_index, MIN_BITS_PER_GROUP), - prettify_bin_addr(ref_offset, MIN_BITS_PER_GROUP), + BinaryAddress.prettify(ref.bin_addr, MIN_BITS_PER_GROUP), + BinaryAddress.prettify(ref_tag, MIN_BITS_PER_GROUP), + BinaryAddress.prettify(ref_index, MIN_BITS_PER_GROUP), + BinaryAddress.prettify(ref_offset, MIN_BITS_PER_GROUP), ref_status)) print(table) diff --git a/cachesimulator/table.py b/cachesimulator/table.py index 3569b45..4c0529f 100644 --- a/cachesimulator/table.py +++ b/cachesimulator/table.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +import sys + # A class for displaying ASCII tables class Table(object): diff --git a/cachesimulator/word_addr.py b/cachesimulator/word_addr.py new file mode 100644 index 0000000..58c18a4 --- /dev/null +++ b/cachesimulator/word_addr.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + + +class WordAddress(int): + + def __init__(self, value): + self.value = value + + # Retrieves all consecutive words for the given word address (including + # itself) + def get_consecutive_words(self, num_words_per_block): + + offset = self.value % num_words_per_block + return [(self.value - offset + i) for i in range(num_words_per_block)] diff --git a/tests/test_simulator_hit.py b/tests/test_simulator_hit.py index bf72408..d334334 100644 --- a/tests/test_simulator_hit.py +++ b/tests/test_simulator_hit.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 import nose.tools as nose + import cachesimulator.simulator as sim +from cachesimulator.cache import Cache def test_ref_status_str(): @@ -14,21 +16,21 @@ class TestIsHit(object): """is_hit should behave correctly in all cases""" def __init__(self): - self.cache = { + self.cache = Cache({ '010': [{ 'tag': '1011', 'data': [180, 181] }] - } + }) def test_is_hit_true(self): """is_hit should return True if index and tag exist in cache""" - nose.assert_true(sim.is_hit(self.cache, '010', '1011')) + nose.assert_true(self.cache.is_hit('010', '1011')) def test_is_hit_false_index_mismatch(self): """is_hit should return False if index does not exist in cache""" - nose.assert_false(sim.is_hit(self.cache, '011', '1011')) + nose.assert_false(self.cache.is_hit('011', '1011')) def test_is_hit_false_tag_mismatch(self): """is_hit should return False if tag does not exist in cache""" - nose.assert_false(sim.is_hit(self.cache, '010', '1010')) + nose.assert_false(self.cache.is_hit('010', '1010')) diff --git a/tests/test_simulator_refs.py b/tests/test_simulator_refs.py index 6a84fb5..b54abb0 100644 --- a/tests/test_simulator_refs.py +++ b/tests/test_simulator_refs.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import nose.tools as nose +from cachesimulator.cache import Cache import cachesimulator.simulator as sim @@ -10,6 +11,7 @@ def test_get_addr_refs(): refs = sim.get_addr_refs( word_addrs=word_addrs, num_addr_bits=8, num_tag_bits=4, num_index_bits=3, num_offset_bits=1) + print(refs) ref = refs[1] nose.assert_equal(len(refs), len(word_addrs)) nose.assert_equal(ref.word_addr, 180) @@ -38,7 +40,7 @@ def test_read_refs_into_cache_direct_mapped_lru(self): cache, ref_statuses = sim.read_refs_into_cache( refs=refs, num_sets=4, num_blocks_per_set=1, num_words_per_block=1, num_index_bits=2, replacement_policy='lru') - nose.assert_dict_equal(cache, { + nose.assert_equal(cache, { '00': [ {'tag': '10', 'data': [8]} ], @@ -58,7 +60,7 @@ def test_read_refs_into_cache_set_associative_lru(self): cache, ref_statuses = sim.read_refs_into_cache( refs=refs, num_sets=4, num_blocks_per_set=3, num_words_per_block=2, num_index_bits=2, replacement_policy='lru') - nose.assert_dict_equal(cache, { + nose.assert_equal(cache, { '00': [ {'tag': '01011', 'data': [88, 89]} ], @@ -87,7 +89,7 @@ def test_read_refs_into_cache_fully_associative_lru(self): cache, ref_statuses = sim.read_refs_into_cache( refs=refs, num_sets=1, num_blocks_per_set=4, num_words_per_block=2, num_index_bits=0, replacement_policy='lru') - nose.assert_dict_equal(cache, { + nose.assert_equal(cache, { '0': [ {'tag': '1011010', 'data': [180, 181]}, {'tag': '0010110', 'data': [44, 45]}, @@ -105,12 +107,12 @@ def test_read_refs_into_cache_fully_associative_mru(self): cache, ref_statuses = sim.read_refs_into_cache( refs=refs, num_sets=1, num_blocks_per_set=4, num_words_per_block=2, num_index_bits=0, replacement_policy='mru') - nose.assert_dict_equal(cache, { + nose.assert_equal(cache, Cache({ '0': [ {'tag': '0000001', 'data': [2, 3]}, {'tag': '1111110', 'data': [252, 253]}, {'tag': '0010101', 'data': [42, 43]}, {'tag': '0000111', 'data': [14, 15]} ] - }) + })) nose.assert_set_equal(self.get_hits(ref_statuses), {3, 8}) diff --git a/tests/test_simulator_set_block.py b/tests/test_simulator_set_block.py index 92c6c65..4f12d81 100644 --- a/tests/test_simulator_set_block.py +++ b/tests/test_simulator_set_block.py @@ -2,21 +2,22 @@ import copy import nose.tools as nose -import cachesimulator.simulator as sim + +from cachesimulator.cache import Cache class TestSetBlock(object): """set_block should behave correctly in all cases""" def reset(self): - self.cache = { + self.cache = Cache({ '010': [ {'tag': '1000'}, {'tag': '1100'}, {'tag': '1101'}, {'tag': '1110'} ] - } + }) self.recently_used_addrs = [ ('100', '1100'), ('010', '1101'), @@ -28,28 +29,26 @@ def test_empty_set(self): """set_block should add new block if index set is empty""" self.reset() self.cache['010'][:] = [] - sim.set_block( - cache=self.cache, + self.cache.set_block( recently_used_addrs=[], replacement_policy='lru', num_blocks_per_set=4, addr_index='010', new_entry=self.new_entry) - nose.assert_dict_equal(self.cache, { + nose.assert_equal(self.cache, { '010': [{'tag': '1111'}] }) def test_lru_replacement(self): """set_block should perform LRU replacement as needed""" self.reset() - sim.set_block( - cache=self.cache, + self.cache.set_block( recently_used_addrs=self.recently_used_addrs, replacement_policy='lru', num_blocks_per_set=4, addr_index='010', new_entry=self.new_entry) - nose.assert_dict_equal(self.cache, { + nose.assert_equal(self.cache, { '010': [ {'tag': '1000'}, {'tag': '1100'}, @@ -61,14 +60,13 @@ def test_lru_replacement(self): def test_mru_replacement(self): """set_block should optionally perform MRU replacement as needed""" self.reset() - sim.set_block( - cache=self.cache, + self.cache.set_block( recently_used_addrs=self.recently_used_addrs, replacement_policy='mru', num_blocks_per_set=4, addr_index='010', new_entry=self.new_entry) - nose.assert_dict_equal(self.cache, { + nose.assert_equal(self.cache, { '010': [ {'tag': '1000'}, {'tag': '1100'}, @@ -81,11 +79,10 @@ def test_no_replacement(self): """set_block should not perform replacement if there are no recents""" self.reset() original_cache = copy.deepcopy(self.cache) - sim.set_block( - cache=self.cache, + self.cache.set_block( recently_used_addrs=[], replacement_policy='lru', num_blocks_per_set=4, addr_index='010', new_entry=self.new_entry) - nose.assert_dict_equal(self.cache, original_cache) + nose.assert_equal(self.cache, original_cache) diff --git a/tests/test_simulator_utility.py b/tests/test_simulator_utility.py index 94f3ea2..aeff24e 100644 --- a/tests/test_simulator_utility.py +++ b/tests/test_simulator_utility.py @@ -1,113 +1,115 @@ #!/usr/bin/env python3 import nose.tools as nose -import cachesimulator.simulator as sim + +from cachesimulator.bin_addr import BinaryAddress +from cachesimulator.word_addr import WordAddress def test_get_bin_addr_unpadded(): """get_bin_addr should return unpadded binary address of word address""" nose.assert_equal( - sim.get_bin_addr(180), + BinaryAddress(word_addr=WordAddress(180)), '10110100') def test_get_bin_addr_padded(): """get_bin_addr should return padded binary address of word address""" nose.assert_equal( - sim.get_bin_addr(44, num_addr_bits=8), + BinaryAddress(word_addr=WordAddress(44), num_addr_bits=8), '00101100') def test_prettify_bin_addr_16_bit(): """prettify_bin_addr should prettify 8-bit string into groups of 3""" nose.assert_equal( - sim.prettify_bin_addr('1010101110101011', min_bits_per_group=3), + BinaryAddress.prettify('1010101110101011', min_bits_per_group=3), '1010 1011 1010 1011') def test_prettify_bin_addr_8_bit(): """prettify_bin_addr should prettify 8-bit string into groups of 3""" nose.assert_equal( - sim.prettify_bin_addr('10101011', min_bits_per_group=3), + BinaryAddress.prettify('10101011', min_bits_per_group=3), '1010 1011') def test_prettify_bin_addr_7_bit(): """prettify_bin_addr should prettify 7-bit string into groups of 3""" nose.assert_equal( - sim.prettify_bin_addr('1011010', min_bits_per_group=3), + BinaryAddress.prettify('1011010', min_bits_per_group=3), '101 1010') def test_prettify_bin_addr_6_bit(): """prettify_bin_addr should prettify 6-bit string into groups of 3""" nose.assert_equal( - sim.prettify_bin_addr('101011', min_bits_per_group=3), + BinaryAddress.prettify('101011', min_bits_per_group=3), '101 011') def test_prettify_bin_addr_5_bit(): """prettify_bin_addr should prettify 5-bit string into groups of 3""" nose.assert_equal( - sim.prettify_bin_addr('10110', min_bits_per_group=3), + BinaryAddress.prettify('10110', min_bits_per_group=3), '10110') def test_get_tag_5_bit(): """get_tag should return correct 5 tag bits for an address""" nose.assert_equal( - sim.get_tag('10110100', num_tag_bits=5), + BinaryAddress('10110100').get_tag(num_tag_bits=5), '10110') def test_get_tag_0_bit(): """get_tag should return None if no bits are allocated to a tag""" nose.assert_is_none( - sim.get_tag('10110100', num_tag_bits=0)) + BinaryAddress('10110100').get_tag(num_tag_bits=0)) def test_get_index_2_bit(): """get_index should return correct 2 index bits for an address""" nose.assert_equal( - sim.get_index('11111101', num_offset_bits=1, num_index_bits=2), - '10') + BinaryAddress('11111101').get_index( + num_offset_bits=1, num_index_bits=2), '10') def test_get_index_0_bit(): """get_index should return None if no bits are allocated to an index""" nose.assert_is_none( - sim.get_index('11111111', num_offset_bits=1, num_index_bits=0)) + BinaryAddress('11111111').get_index( + num_offset_bits=1, num_index_bits=0)) def test_get_offset_2_bit(): """get_offset should return correct 2 offset bits for an address""" nose.assert_equal( - sim.get_offset('11111101', num_offset_bits=2), - '01') + BinaryAddress('11111101').get_offset(num_offset_bits=2), '01') def test_get_offset_0_bit(): """get_offset should return None if no bits are allocated to an offset""" nose.assert_is_none( - sim.get_offset('10110100', num_offset_bits=0)) + BinaryAddress('10110100').get_offset(num_offset_bits=0)) def test_get_consecutive_words_1_word(): """get_consecutive_words should return same word for 1-word blocks""" nose.assert_list_equal( - sim.get_consecutive_words(23, num_words_per_block=1), + WordAddress(23).get_consecutive_words(num_words_per_block=1), [23]) def test_get_consecutive_words_2_word(): """get_consecutive_words should return correct words for 2-word blocks""" nose.assert_list_equal( - sim.get_consecutive_words(22, num_words_per_block=2), + WordAddress(22).get_consecutive_words(num_words_per_block=2), [22, 23]) def test_get_consecutive_words_4_word(): """get_consecutive_words should return correct words for 4-word blocks""" nose.assert_list_equal( - sim.get_consecutive_words(21, num_words_per_block=4), + WordAddress(21).get_consecutive_words(num_words_per_block=4), [20, 21, 22, 23]) From 4433663df0411418921b998d2e02bb04f59461f6 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 14:50:46 -0800 Subject: [PATCH 03/31] Rewrite BinaryAddress class to inherit from str --- cachesimulator/bin_addr.py | 45 +++++++++++--------------------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/cachesimulator/bin_addr.py b/cachesimulator/bin_addr.py index 2a73b62..01c0007 100644 --- a/cachesimulator/bin_addr.py +++ b/cachesimulator/bin_addr.py @@ -1,37 +1,18 @@ #!/usr/bin/env python3 -class BinaryAddress(object): +class BinaryAddress(str): # Retrieves the binary address of a certain length for a base-10 word - # address - def __init__(self, bin_addr=None, word_addr=None, num_addr_bits=0): + # address; we must define __new__ instead of __init__ because the class we + # are inheriting from (str) is an immutable data type + def __new__(cls, bin_addr=None, word_addr=None, num_addr_bits=0): if word_addr is not None: - self.value = bin(word_addr.value)[2:].zfill(num_addr_bits) + return str.__new__( + cls, bin(word_addr.value)[2:].zfill(num_addr_bits)) else: - self.value = bin_addr - - def __repr__(self): - return repr(self.value) - - def __format__(self, formatstr): - return str.__format__(self.value, formatstr) - - def __hash__(self): - return hash(self.value) - - def __len__(self): - return len(self.value) - - def __getitem__(self, key): - return self.value[key] - - def __eq__(self, other): - return self.value == other - - def __lt__(self, other): - return self.value < other.value + return str.__new__(cls, bin_addr) @classmethod def prettify(cls, bin_addr, min_bits_per_group): @@ -52,7 +33,7 @@ def prettify(cls, bin_addr, min_bits_per_group): def get_tag(self, num_tag_bits): end = num_tag_bits - tag = self.value[:end] + tag = self[:end] if len(tag) != 0: return tag else: @@ -61,9 +42,9 @@ def get_tag(self, num_tag_bits): # Retrieves the index used to group blocks in the cache def get_index(self, num_offset_bits, num_index_bits): - start = len(self.value) - num_offset_bits - num_index_bits - end = len(self.value) - num_offset_bits - index = self.value[start:end] + start = len(self) - num_offset_bits - num_index_bits + end = len(self) - num_offset_bits + index = self[start:end] if len(index) != 0: return index else: @@ -73,8 +54,8 @@ def get_index(self, num_offset_bits, num_index_bits): # the given binary address def get_offset(self, num_offset_bits): - start = len(self.value) - num_offset_bits - offset = self.value[start:] + start = len(self) - num_offset_bits + offset = self[start:] if len(offset) != 0: return offset else: From ad6b74449c70054364f784ac6f4e831c2550107c Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 14:57:57 -0800 Subject: [PATCH 04/31] Rely on default int constructor for WordAddress --- cachesimulator/bin_addr.py | 2 +- cachesimulator/word_addr.py | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/cachesimulator/bin_addr.py b/cachesimulator/bin_addr.py index 01c0007..ff94ad1 100644 --- a/cachesimulator/bin_addr.py +++ b/cachesimulator/bin_addr.py @@ -10,7 +10,7 @@ def __new__(cls, bin_addr=None, word_addr=None, num_addr_bits=0): if word_addr is not None: return str.__new__( - cls, bin(word_addr.value)[2:].zfill(num_addr_bits)) + cls, bin(word_addr)[2:].zfill(num_addr_bits)) else: return str.__new__(cls, bin_addr) diff --git a/cachesimulator/word_addr.py b/cachesimulator/word_addr.py index 58c18a4..a57bc16 100644 --- a/cachesimulator/word_addr.py +++ b/cachesimulator/word_addr.py @@ -3,12 +3,9 @@ class WordAddress(int): - def __init__(self, value): - self.value = value - # Retrieves all consecutive words for the given word address (including # itself) def get_consecutive_words(self, num_words_per_block): - offset = self.value % num_words_per_block - return [(self.value - offset + i) for i in range(num_words_per_block)] + offset = self % num_words_per_block + return [(self - offset + i) for i in range(num_words_per_block)] From c60d787ffd49b1244f11a4dfca2d3507a7edf19a Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 15:04:09 -0800 Subject: [PATCH 05/31] Replace redundant str instances with super() --- cachesimulator/bin_addr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cachesimulator/bin_addr.py b/cachesimulator/bin_addr.py index ff94ad1..e4cc3cc 100644 --- a/cachesimulator/bin_addr.py +++ b/cachesimulator/bin_addr.py @@ -9,10 +9,10 @@ class BinaryAddress(str): def __new__(cls, bin_addr=None, word_addr=None, num_addr_bits=0): if word_addr is not None: - return str.__new__( + return super().__new__( cls, bin(word_addr)[2:].zfill(num_addr_bits)) else: - return str.__new__(cls, bin_addr) + return super().__new__(cls, bin_addr) @classmethod def prettify(cls, bin_addr, min_bits_per_group): From d84e5081946b627111e02ef87d1a0f031a5d64ee Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 15:14:08 -0800 Subject: [PATCH 06/31] Enforce PEP 257 import rules with isort --- cachesimulator/cache.py | 2 +- cachesimulator/simulator.py | 2 +- requirements.txt | 3 +++ tests/test_compliance.py | 1 + tests/test_simulator_display.py | 3 ++- tests/test_simulator_main.py | 4 +++- tests/test_simulator_refs.py | 3 ++- tests/test_simulator_set_block.py | 1 + tests/test_table.py | 1 + 9 files changed, 15 insertions(+), 5 deletions(-) diff --git a/cachesimulator/cache.py b/cachesimulator/cache.py index 8066640..19d253a 100644 --- a/cachesimulator/cache.py +++ b/cachesimulator/cache.py @@ -2,8 +2,8 @@ import copy -from cachesimulator.word_addr import WordAddress from cachesimulator.bin_addr import BinaryAddress +from cachesimulator.word_addr import WordAddress class Cache(object): diff --git a/cachesimulator/simulator.py b/cachesimulator/simulator.py index f519b75..9ed78c2 100755 --- a/cachesimulator/simulator.py +++ b/cachesimulator/simulator.py @@ -5,10 +5,10 @@ import shutil from enum import Enum -from cachesimulator.word_addr import WordAddress from cachesimulator.bin_addr import BinaryAddress from cachesimulator.cache import Cache from cachesimulator.table import Table +from cachesimulator.word_addr import WordAddress # The names of all reference table columns REF_COL_NAMES = ('WordAddr', 'BinAddr', 'Tag', 'Index', 'Offset', 'Hit/Miss') diff --git a/requirements.txt b/requirements.txt index 35e658c..3290f3c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,9 @@ colorama==0.3.9 coverage==4.5.1 flake8==3.5.0 +flake8-isort==2.4 flake8-polyfill==1.0.2 +isort==4.3.4 mando==0.6.4 mccabe==0.6.1 nose==1.3.7 @@ -11,3 +13,4 @@ radon==2.2.0 rednose==1.3.0 six==1.11.0 termstyle==0.1.11 +testfixtures==5.4.0 diff --git a/tests/test_compliance.py b/tests/test_compliance.py index e71af86..5464458 100644 --- a/tests/test_compliance.py +++ b/tests/test_compliance.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import glob + import nose.tools as nose import pycodestyle import radon.complexity as radon diff --git a/tests/test_simulator_display.py b/tests/test_simulator_display.py index 266c44a..54310cd 100644 --- a/tests/test_simulator_display.py +++ b/tests/test_simulator_display.py @@ -2,9 +2,10 @@ import contextlib import io + import nose.tools as nose -import cachesimulator.simulator as sim +import cachesimulator.simulator as sim WORD_ADDRS = [43, 14, 253, 186] TABLE_WIDTH = 80 diff --git a/tests/test_simulator_main.py b/tests/test_simulator_main.py index 562ce1c..0b4083a 100644 --- a/tests/test_simulator_main.py +++ b/tests/test_simulator_main.py @@ -2,9 +2,11 @@ import contextlib import io +from unittest.mock import patch + import nose.tools as nose + import cachesimulator.simulator as sim -from unittest.mock import patch @patch('sys.argv', [ diff --git a/tests/test_simulator_refs.py b/tests/test_simulator_refs.py index b54abb0..cb38d3e 100644 --- a/tests/test_simulator_refs.py +++ b/tests/test_simulator_refs.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 import nose.tools as nose -from cachesimulator.cache import Cache + import cachesimulator.simulator as sim +from cachesimulator.cache import Cache def test_get_addr_refs(): diff --git a/tests/test_simulator_set_block.py b/tests/test_simulator_set_block.py index 4f12d81..367c3ca 100644 --- a/tests/test_simulator_set_block.py +++ b/tests/test_simulator_set_block.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import copy + import nose.tools as nose from cachesimulator.cache import Cache diff --git a/tests/test_table.py b/tests/test_table.py index 3c37bfe..18ff515 100644 --- a/tests/test_table.py +++ b/tests/test_table.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import nose.tools as nose + from cachesimulator.table import Table From 611da36a0dbcee4cf04b3da09e64567dff879de0 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 15:36:34 -0800 Subject: [PATCH 07/31] Ensure that test deepcopy is functioning properly --- tests/test_simulator_set_block.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_simulator_set_block.py b/tests/test_simulator_set_block.py index 367c3ca..2befaf4 100644 --- a/tests/test_simulator_set_block.py +++ b/tests/test_simulator_set_block.py @@ -86,4 +86,5 @@ def test_no_replacement(self): num_blocks_per_set=4, addr_index='010', new_entry=self.new_entry) + nose.assert_is_not(self.cache, original_cache) nose.assert_equal(self.cache, original_cache) From c722f5b99322315a3e7ddfc78ff199642dba3013 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 15:37:37 -0800 Subject: [PATCH 08/31] Inherit Cache from dict (successfully) --- cachesimulator/cache.py | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/cachesimulator/cache.py b/cachesimulator/cache.py index 19d253a..7b7c832 100644 --- a/cachesimulator/cache.py +++ b/cachesimulator/cache.py @@ -1,42 +1,22 @@ #!/usr/bin/env python3 -import copy - from cachesimulator.bin_addr import BinaryAddress from cachesimulator.word_addr import WordAddress -class Cache(object): +class Cache(dict): # Initializes the reference cache with a fixed number of sets def __init__(self, cache=None, num_sets=None, num_index_bits=None): if cache is not None: - self.map = cache + self.update(cache) else: self.map = {} for i in range(num_sets): index = BinaryAddress( word_addr=WordAddress(i), num_addr_bits=num_index_bits) - self.map[index] = [] - - def __eq__(self, other): - return self.map == other - - def __len__(self): - return len(self.map) - - def __getitem__(self, key): - return self.map[key] - - def __contains__(self, value): - return value in self.map - - def __deepcopy__(self, memodict={}): - return copy.deepcopy(self.map) - - def keys(self): - return self.map.keys() + self[index] = [] # Returns True if a block at the given index and tag exists in the cache, # indicating a hit; returns False otherwise, indicating a miss From 929f72bc81817e9382063564a9a302ba2c5a1586 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 15:40:00 -0800 Subject: [PATCH 09/31] Use integer default for Cache num_index_bits param --- cachesimulator/cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cachesimulator/cache.py b/cachesimulator/cache.py index 7b7c832..3fe7c9b 100644 --- a/cachesimulator/cache.py +++ b/cachesimulator/cache.py @@ -7,7 +7,7 @@ class Cache(dict): # Initializes the reference cache with a fixed number of sets - def __init__(self, cache=None, num_sets=None, num_index_bits=None): + def __init__(self, cache=None, num_sets=None, num_index_bits=0): if cache is not None: self.update(cache) From 053dfb8f286cced57a5ed2ea3891eb16727921eb Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 15:42:14 -0800 Subject: [PATCH 10/31] Remove obsolete Cache.map property Cache.map was made obsolete in c722f5b, since Cache now properly inherits from dict. --- cachesimulator/cache.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cachesimulator/cache.py b/cachesimulator/cache.py index 3fe7c9b..0230cb9 100644 --- a/cachesimulator/cache.py +++ b/cachesimulator/cache.py @@ -12,7 +12,6 @@ def __init__(self, cache=None, num_sets=None, num_index_bits=0): if cache is not None: self.update(cache) else: - self.map = {} for i in range(num_sets): index = BinaryAddress( word_addr=WordAddress(i), num_addr_bits=num_index_bits) From 76722fc9f057d6cec481d65dbc58195006c6f0fc Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 15:49:52 -0800 Subject: [PATCH 11/31] Replace hardcoded reference to RefStatus with self --- cachesimulator/simulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cachesimulator/simulator.py b/cachesimulator/simulator.py index 9ed78c2..30eda8f 100755 --- a/cachesimulator/simulator.py +++ b/cachesimulator/simulator.py @@ -26,7 +26,7 @@ class RefStatus(Enum): # Define how reference statuses are displayed in simulation results def __str__(self): - if self.value == RefStatus.hit.value: + if self.value == self.hit.value: return 'HIT' else: return 'miss' From de7dd87d309a15dc9fa1b241b847c4a7bbdc24a6 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 16:07:10 -0800 Subject: [PATCH 12/31] Streamline call to run simulation in main() --- cachesimulator/simulator.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/cachesimulator/simulator.py b/cachesimulator/simulator.py index 30eda8f..2496483 100755 --- a/cachesimulator/simulator.py +++ b/cachesimulator/simulator.py @@ -249,13 +249,7 @@ def parse_cli_args(): def main(): cli_args = parse_cli_args() - run_simulation( - num_blocks_per_set=cli_args.num_blocks_per_set, - num_words_per_block=cli_args.num_words_per_block, - cache_size=cli_args.cache_size, - replacement_policy=cli_args.replacement_policy, - num_addr_bits=cli_args.num_addr_bits, - word_addrs=cli_args.word_addrs) + run_simulation(**vars(cli_args)) if __name__ == '__main__': From 3ce1ee9783996de3963b97107690dd6dbb084e51 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 16:09:24 -0800 Subject: [PATCH 13/31] Update copyright year to 2018 --- LICENSE.txt | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 28bdd8b..2a4a546 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015-2016 Caleb Evans +Copyright (c) 2015-2018 Caleb Evans Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 3f9e3c9..d6d1a5f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Cache Simulator -*Copyright 2015-2016 Caleb Evans* +*Copyright 2015-2018 Caleb Evans* *Released under the MIT license* [![Build Status](https://travis-ci.org/caleb531/cache-simulator.svg?branch=master)](https://travis-ci.org/caleb531/cache-simulator) From c7e511263b44aa6187ff64fe968322b2b108ef19 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 16:11:32 -0800 Subject: [PATCH 14/31] Revert "Replace hardcoded reference to RefStatus with self" This reverts commit 76722fc9f057d6cec481d65dbc58195006c6f0fc, as it broke the program in Python 3.4. --- cachesimulator/simulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cachesimulator/simulator.py b/cachesimulator/simulator.py index 2496483..554ba81 100755 --- a/cachesimulator/simulator.py +++ b/cachesimulator/simulator.py @@ -26,7 +26,7 @@ class RefStatus(Enum): # Define how reference statuses are displayed in simulation results def __str__(self): - if self.value == self.hit.value: + if self.value == RefStatus.hit.value: return 'HIT' else: return 'miss' From 5c3b681ca7f4e489b94b9e3ec6076f50f86848c5 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 16:34:19 -0800 Subject: [PATCH 15/31] Use glob for include list in coveragerc --- .coveragerc | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.coveragerc b/.coveragerc index 81c47ab..a3bc7eb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -14,8 +14,4 @@ exclude_lines = # Only check coverage for source files include = - cachesimulator/simulator.py - cachesimulator/word_addr.py - cachesimulator/bin_addr.py - cachesimulator/cache.py - cachesimulator/table.py + cachesimulator/*.py From 2959daec51b481029e0fe2450887142af4252508 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 17:02:36 -0800 Subject: [PATCH 16/31] Remove leftover print() statement in test --- tests/test_simulator_refs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_simulator_refs.py b/tests/test_simulator_refs.py index cb38d3e..8f69ea9 100644 --- a/tests/test_simulator_refs.py +++ b/tests/test_simulator_refs.py @@ -12,7 +12,6 @@ def test_get_addr_refs(): refs = sim.get_addr_refs( word_addrs=word_addrs, num_addr_bits=8, num_tag_bits=4, num_index_bits=3, num_offset_bits=1) - print(refs) ref = refs[1] nose.assert_equal(len(refs), len(word_addrs)) nose.assert_equal(ref.word_addr, 180) From ba115d9bccac7fd5615841df86200b87c2a92282 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 17:06:56 -0800 Subject: [PATCH 17/31] Make Reference class inherit from dict --- cachesimulator/simulator.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cachesimulator/simulator.py b/cachesimulator/simulator.py index 554ba81..68a5816 100755 --- a/cachesimulator/simulator.py +++ b/cachesimulator/simulator.py @@ -33,7 +33,7 @@ def __str__(self): # An address reference consisting of the address and all of its components -class Reference(object): +class Reference(dict): def __init__(self, word_addr, num_addr_bits, num_offset_bits, num_index_bits, num_tag_bits): @@ -44,9 +44,6 @@ def __init__(self, word_addr, num_addr_bits, self.index = self.bin_addr.get_index(num_offset_bits, num_index_bits) self.tag = self.bin_addr.get_tag(num_tag_bits) - def __repr__(self): - return repr(self.__dict__) - # Retrieves a list of address references for use by simulator def get_addr_refs(word_addrs, num_addr_bits, From 4dc893a0f0420b161a5d6841decb34711620f23e Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 17:26:10 -0800 Subject: [PATCH 18/31] Move Reference and RefStatus classes into separate files Also rename RefStatus to ReferenceCacheStatus for greater clarity. --- cachesimulator/reference.py | 33 +++++++++++++++++++++++++++++++++ cachesimulator/simulator.py | 34 +++------------------------------- tests/test_simulator_hit.py | 6 +++--- 3 files changed, 39 insertions(+), 34 deletions(-) create mode 100644 cachesimulator/reference.py diff --git a/cachesimulator/reference.py b/cachesimulator/reference.py new file mode 100644 index 0000000..7f8cb6b --- /dev/null +++ b/cachesimulator/reference.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +from enum import Enum + +from cachesimulator.bin_addr import BinaryAddress +from cachesimulator.word_addr import WordAddress + + +# An address reference consisting of the address and all of its components +class Reference(dict): + + def __init__(self, word_addr, num_addr_bits, + num_offset_bits, num_index_bits, num_tag_bits): + self.word_addr = WordAddress(word_addr) + self.bin_addr = BinaryAddress( + word_addr=self.word_addr, num_addr_bits=num_addr_bits) + self.offset = self.bin_addr.get_offset(num_offset_bits) + self.index = self.bin_addr.get_index(num_offset_bits, num_index_bits) + self.tag = self.bin_addr.get_tag(num_tag_bits) + + +# An enum representing the cache status of a reference (i.e. hit or miss) +class ReferenceCacheStatus(Enum): + + miss = 0 + hit = 1 + + # Define how reference statuses are displayed in simulation results + def __str__(self): + if self.value == ReferenceCacheStatus.hit.value: + return 'HIT' + else: + return 'miss' diff --git a/cachesimulator/simulator.py b/cachesimulator/simulator.py index 68a5816..c81bd92 100755 --- a/cachesimulator/simulator.py +++ b/cachesimulator/simulator.py @@ -3,12 +3,11 @@ import argparse import math import shutil -from enum import Enum from cachesimulator.bin_addr import BinaryAddress from cachesimulator.cache import Cache +from cachesimulator.reference import Reference, ReferenceCacheStatus from cachesimulator.table import Table -from cachesimulator.word_addr import WordAddress # The names of all reference table columns REF_COL_NAMES = ('WordAddr', 'BinAddr', 'Tag', 'Index', 'Offset', 'Hit/Miss') @@ -18,33 +17,6 @@ DEFAULT_TABLE_WIDTH = 80 -# An enum representing the cache status of a reference (i.e. hit or miss) -class RefStatus(Enum): - - miss = 0 - hit = 1 - - # Define how reference statuses are displayed in simulation results - def __str__(self): - if self.value == RefStatus.hit.value: - return 'HIT' - else: - return 'miss' - - -# An address reference consisting of the address and all of its components -class Reference(dict): - - def __init__(self, word_addr, num_addr_bits, - num_offset_bits, num_index_bits, num_tag_bits): - self.word_addr = WordAddress(word_addr) - self.bin_addr = BinaryAddress( - word_addr=self.word_addr, num_addr_bits=num_addr_bits) - self.offset = self.bin_addr.get_offset(num_offset_bits) - self.index = self.bin_addr.get_index(num_offset_bits, num_index_bits) - self.tag = self.bin_addr.get_tag(num_tag_bits) - - # Retrieves a list of address references for use by simulator def get_addr_refs(word_addrs, num_addr_bits, num_offset_bits, num_index_bits, num_tag_bits): @@ -83,9 +55,9 @@ def read_refs_into_cache(num_sets, num_blocks_per_set, num_index_bits, # Determine the Hit/Miss value for this address to display in the table if cache.is_hit(ref.index, ref.tag): # Give emphasis to hits in contrast to misses - ref_status = RefStatus.hit + ref_status = ReferenceCacheStatus.hit else: - ref_status = RefStatus.miss + ref_status = ReferenceCacheStatus.miss # Create entry dictionary containing tag and data for this address entry = { 'tag': ref.tag, diff --git a/tests/test_simulator_hit.py b/tests/test_simulator_hit.py index d334334..55bf822 100644 --- a/tests/test_simulator_hit.py +++ b/tests/test_simulator_hit.py @@ -7,9 +7,9 @@ def test_ref_status_str(): - """RefStatus enum members should display correct string values""" - nose.assert_equal(str(sim.RefStatus.hit), 'HIT') - nose.assert_equal(str(sim.RefStatus.miss), 'miss') + """cache status enum members should display correct string values""" + nose.assert_equal(str(sim.ReferenceCacheStatus.hit), 'HIT') + nose.assert_equal(str(sim.ReferenceCacheStatus.miss), 'miss') class TestIsHit(object): From afa505fc0805cb283e5ee266ce04134429d1e181 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 17:39:00 -0800 Subject: [PATCH 19/31] Ignore replacement policy argument case For example, "mru" and "MRU" are interchangeable. --- cachesimulator/simulator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cachesimulator/simulator.py b/cachesimulator/simulator.py index c81bd92..5aaa5ff 100755 --- a/cachesimulator/simulator.py +++ b/cachesimulator/simulator.py @@ -210,6 +210,8 @@ def parse_cli_args(): '--replacement-policy', choices=('lru', 'mru'), default='lru', + # Ignore argument case (e.g. "mru" and "MRU" are equivalent) + type=str.lower, help='the cache replacement policy (LRU or MRU)') return parser.parse_args() From ed4f8d0d5ff848d14e5acfcf825fc068073b2bf7 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 4 Mar 2018 19:40:01 -0800 Subject: [PATCH 20/31] Remove unused sys import --- cachesimulator/table.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cachesimulator/table.py b/cachesimulator/table.py index 4c0529f..3569b45 100644 --- a/cachesimulator/table.py +++ b/cachesimulator/table.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -import sys - # A class for displaying ASCII tables class Table(object): From 4e9dc56c62e9082440076f65901f9855f1e73262 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Thu, 8 Mar 2018 18:42:54 -0800 Subject: [PATCH 21/31] Prefer generic equality assertion It already performs type-checking. --- tests/test_simulator_refs.py | 8 ++++---- tests/test_simulator_utility.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_simulator_refs.py b/tests/test_simulator_refs.py index 8f69ea9..ef31713 100644 --- a/tests/test_simulator_refs.py +++ b/tests/test_simulator_refs.py @@ -50,7 +50,7 @@ def test_read_refs_into_cache_direct_mapped_lru(self): ], '11': [] }) - nose.assert_set_equal(self.get_hits(ref_statuses), set()) + nose.assert_equal(self.get_hits(ref_statuses), set()) def test_read_refs_into_cache_set_associative_lru(self): """read_refs_into_cache should work for set associative LRU cache""" @@ -79,7 +79,7 @@ def test_read_refs_into_cache_set_associative_lru(self): {'tag': '00001', 'data': [14, 15]}, ] }) - nose.assert_set_equal(self.get_hits(ref_statuses), {3, 6, 8}) + nose.assert_equal(self.get_hits(ref_statuses), {3, 6, 8}) def test_read_refs_into_cache_fully_associative_lru(self): """read_refs_into_cache should work for fully associative LRU cache""" @@ -97,7 +97,7 @@ def test_read_refs_into_cache_fully_associative_lru(self): {'tag': '1011101', 'data': [186, 187]} ] }) - nose.assert_set_equal(self.get_hits(ref_statuses), {3, 6}) + nose.assert_equal(self.get_hits(ref_statuses), {3, 6}) def test_read_refs_into_cache_fully_associative_mru(self): """read_refs_into_cache should work for fully associative MRU cache""" @@ -115,4 +115,4 @@ def test_read_refs_into_cache_fully_associative_mru(self): {'tag': '0000111', 'data': [14, 15]} ] })) - nose.assert_set_equal(self.get_hits(ref_statuses), {3, 8}) + nose.assert_equal(self.get_hits(ref_statuses), {3, 8}) diff --git a/tests/test_simulator_utility.py b/tests/test_simulator_utility.py index aeff24e..0146de8 100644 --- a/tests/test_simulator_utility.py +++ b/tests/test_simulator_utility.py @@ -96,20 +96,20 @@ def test_get_offset_0_bit(): def test_get_consecutive_words_1_word(): """get_consecutive_words should return same word for 1-word blocks""" - nose.assert_list_equal( + nose.assert_equal( WordAddress(23).get_consecutive_words(num_words_per_block=1), [23]) def test_get_consecutive_words_2_word(): """get_consecutive_words should return correct words for 2-word blocks""" - nose.assert_list_equal( + nose.assert_equal( WordAddress(22).get_consecutive_words(num_words_per_block=2), [22, 23]) def test_get_consecutive_words_4_word(): """get_consecutive_words should return correct words for 4-word blocks""" - nose.assert_list_equal( + nose.assert_equal( WordAddress(21).get_consecutive_words(num_words_per_block=4), [20, 21, 22, 23]) From 17c7f3b9cfebda6f97081b6dca98dc25522bdea4 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 11 Mar 2018 16:36:48 -0700 Subject: [PATCH 22/31] Consolidate simulator functions into class --- cachesimulator/__main__.py | 63 ++++++ cachesimulator/reference.py | 10 +- cachesimulator/simulator.py | 343 +++++++++++++------------------- setup.py | 2 +- tests/test_simulator_display.py | 8 +- tests/test_simulator_main.py | 6 +- tests/test_simulator_refs.py | 7 +- 7 files changed, 231 insertions(+), 208 deletions(-) create mode 100644 cachesimulator/__main__.py diff --git a/cachesimulator/__main__.py b/cachesimulator/__main__.py new file mode 100644 index 0000000..2e336ce --- /dev/null +++ b/cachesimulator/__main__.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +import argparse + +from cachesimulator.simulator import Simulator + + +# Parse command-line arguments passed to the program +def parse_cli_args(): + + parser = argparse.ArgumentParser() + + parser.add_argument( + '--cache-size', + type=int, + required=True, + help='the size of the cache in words') + + parser.add_argument( + '--num-blocks-per-set', + type=int, + default=1, + help='the number of blocks per set') + + parser.add_argument( + '--num-words-per-block', + type=int, + default=1, + help='the number of words per block') + + parser.add_argument( + '--word-addrs', + nargs='+', + type=int, + required=True, + help='one or more base-10 word addresses') + + parser.add_argument( + '--num-addr-bits', + type=int, + default=1, + help='the number of bits in each given word address') + + parser.add_argument( + '--replacement-policy', + choices=('lru', 'mru'), + default='lru', + # Ignore argument case (e.g. "mru" and "MRU" are equivalent) + type=str.lower, + help='the cache replacement policy (LRU or MRU)') + + return parser.parse_args() + + +def main(): + + cli_args = parse_cli_args() + sim = Simulator() + sim.run_simulation(**vars(cli_args)) + + +if __name__ == '__main__': + main() diff --git a/cachesimulator/reference.py b/cachesimulator/reference.py index 7f8cb6b..9fdea4a 100644 --- a/cachesimulator/reference.py +++ b/cachesimulator/reference.py @@ -7,7 +7,7 @@ # An address reference consisting of the address and all of its components -class Reference(dict): +class Reference(object): def __init__(self, word_addr, num_addr_bits, num_offset_bits, num_index_bits, num_tag_bits): @@ -18,6 +18,14 @@ def __init__(self, word_addr, num_addr_bits, self.index = self.bin_addr.get_index(num_offset_bits, num_index_bits) self.tag = self.bin_addr.get_tag(num_tag_bits) + # Return a lightweight entry to store in the cache + def get_cache_entry(self, num_words_per_block): + return { + 'tag': self.tag, + 'data': self.word_addr.get_consecutive_words( + num_words_per_block) + } + # An enum representing the cache status of a reference (i.e. hit or miss) class ReferenceCacheStatus(Enum): diff --git a/cachesimulator/simulator.py b/cachesimulator/simulator.py index 5aaa5ff..1ba3b24 100755 --- a/cachesimulator/simulator.py +++ b/cachesimulator/simulator.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -import argparse import math import shutil @@ -17,211 +16,153 @@ DEFAULT_TABLE_WIDTH = 80 -# Retrieves a list of address references for use by simulator -def get_addr_refs(word_addrs, num_addr_bits, - num_offset_bits, num_index_bits, num_tag_bits): +class Simulator(object): - refs = [] - for word_addr in word_addrs: + def __init__(self): - ref = Reference( - word_addr, num_addr_bits, num_offset_bits, - num_index_bits, num_tag_bits) - refs.append(ref) + # A list of recently ordered addresses, ordered from least-recently + # used to most + self.recently_used_addrs = [] - return refs + # Retrieves a list of address references for use by simulator + def get_addr_refs(self, word_addrs, num_addr_bits, + num_offset_bits, num_index_bits, num_tag_bits): + return [Reference( + word_addr, num_addr_bits, num_offset_bits, + num_index_bits, num_tag_bits) for word_addr in word_addrs] -# Simulate the cache by reading the given address references into it -def read_refs_into_cache(num_sets, num_blocks_per_set, num_index_bits, - num_words_per_block, replacement_policy, refs): - - cache = Cache( - num_sets=num_sets, - num_index_bits=num_index_bits) - - recently_used_addrs = [] - ref_statuses = [] - - for ref in refs: + # Every time we see an address, place it at the top of the + # list of recently-seen addresses + def mark_ref_as_last_seen(self, ref): # The index and tag (not the offset) uniquely identify each address addr_id = (ref.index, ref.tag) - # Add every retrieved address to the list of recently-used addresses - if addr_id in recently_used_addrs: - recently_used_addrs.remove(addr_id) - recently_used_addrs.append(addr_id) - - # Determine the Hit/Miss value for this address to display in the table - if cache.is_hit(ref.index, ref.tag): - # Give emphasis to hits in contrast to misses - ref_status = ReferenceCacheStatus.hit - else: - ref_status = ReferenceCacheStatus.miss - # Create entry dictionary containing tag and data for this address - entry = { - 'tag': ref.tag, - 'data': ref.word_addr.get_consecutive_words( - num_words_per_block) - } - cache.set_block( - recently_used_addrs=recently_used_addrs, - replacement_policy=replacement_policy, - num_blocks_per_set=num_blocks_per_set, - addr_index=ref.index, - new_entry=entry) - - ref_statuses.append(ref_status) - - return cache, ref_statuses - - -# Displays details for each address reference, including its hit/miss status -def display_addr_refs(refs, ref_statuses, table_width): - - table = Table( - num_cols=len(REF_COL_NAMES), width=table_width, alignment='right') - table.header[:] = REF_COL_NAMES - - for ref, ref_status in zip(refs, ref_statuses): - - if ref.tag is not None: - ref_tag = ref.tag - else: - ref_tag = 'n/a' - - if ref.index is not None: - ref_index = ref.index - else: - ref_index = 'n/a' - - if ref.offset is not None: - ref_offset = ref.offset - else: - ref_offset = 'n/a' - - # Display data for each address as a row in the table - table.rows.append(( - ref.word_addr, - BinaryAddress.prettify(ref.bin_addr, MIN_BITS_PER_GROUP), - BinaryAddress.prettify(ref_tag, MIN_BITS_PER_GROUP), - BinaryAddress.prettify(ref_index, MIN_BITS_PER_GROUP), - BinaryAddress.prettify(ref_offset, MIN_BITS_PER_GROUP), - ref_status)) - - print(table) - - -# Displays the contents of the given cache as nicely-formatted table -def display_cache(cache, table_width): - - table = Table( - num_cols=len(cache), width=table_width, alignment='center') - table.title = 'Cache' - - cache_set_names = sorted(cache.keys()) - # A cache containing only one set is considered a fully associative cache - if len(cache) != 1: - # Display set names in table header if cache is not fully associative - table.header[:] = cache_set_names - - # Add to table the cache entries for each block - table.rows.append([]) - for index in cache_set_names: - blocks = cache[index] - table.rows[0].append( - ' '.join(','.join(map(str, entry['data'])) for entry in blocks)) - - print(table) - - -# Run the entire cache simulation -def run_simulation(num_blocks_per_set, num_words_per_block, cache_size, - replacement_policy, num_addr_bits, word_addrs): - - num_blocks = cache_size // num_words_per_block - num_sets = num_blocks // num_blocks_per_set - - # Ensure that the number of bits used to represent each address is always - # large enough to represent the largest address - num_addr_bits = max(num_addr_bits, int(math.log2(max(word_addrs))) + 1) - - num_offset_bits = int(math.log2(num_words_per_block)) - num_index_bits = int(math.log2(num_sets)) - num_tag_bits = num_addr_bits - num_index_bits - num_offset_bits - - refs = get_addr_refs( - word_addrs, num_addr_bits, - num_offset_bits, num_index_bits, num_tag_bits) - - cache, ref_statuses = read_refs_into_cache( - num_sets, num_blocks_per_set, num_index_bits, - num_words_per_block, replacement_policy, refs) - - # The character-width of all displayed tables - # Attempt to fit table to terminal width, otherwise use default of 80 - table_width = max((shutil.get_terminal_size( - (DEFAULT_TABLE_WIDTH, 20)).columns, DEFAULT_TABLE_WIDTH)) - - print() - display_addr_refs(refs, ref_statuses, table_width) - print() - display_cache(cache, table_width) - print() - - -# Parse command-line arguments passed to the program -def parse_cli_args(): - - parser = argparse.ArgumentParser() - - parser.add_argument( - '--cache-size', - type=int, - required=True, - help='the size of the cache in words') - - parser.add_argument( - '--num-blocks-per-set', - type=int, - default=1, - help='the number of blocks per set') - - parser.add_argument( - '--num-words-per-block', - type=int, - default=1, - help='the number of words per block') - - parser.add_argument( - '--word-addrs', - nargs='+', - type=int, - required=True, - help='one or more base-10 word addresses') - - parser.add_argument( - '--num-addr-bits', - type=int, - default=1, - help='the number of bits in each given word address') - - parser.add_argument( - '--replacement-policy', - choices=('lru', 'mru'), - default='lru', - # Ignore argument case (e.g. "mru" and "MRU" are equivalent) - type=str.lower, - help='the cache replacement policy (LRU or MRU)') - - return parser.parse_args() - - -def main(): - - cli_args = parse_cli_args() - run_simulation(**vars(cli_args)) - - -if __name__ == '__main__': - main() + if addr_id in self.recently_used_addrs: + self.recently_used_addrs.remove(addr_id) + self.recently_used_addrs.append(addr_id) + + # Simulate the cache by reading the given address references into it + def read_refs_into_cache(self, num_sets, num_blocks_per_set, + num_index_bits, num_words_per_block, + replacement_policy, refs): + + cache = Cache( + num_sets=num_sets, + num_index_bits=num_index_bits) + ref_statuses = [] + + for ref in refs: + self.mark_ref_as_last_seen(ref) + + # Record if the reference is already in the cache or not + if cache.is_hit(ref.index, ref.tag): + # Give emphasis to hits in contrast to misses + ref_status = ReferenceCacheStatus.hit + else: + ref_status = ReferenceCacheStatus.miss + cache.set_block( + recently_used_addrs=self.recently_used_addrs, + replacement_policy=replacement_policy, + num_blocks_per_set=num_blocks_per_set, + addr_index=ref.index, + new_entry=ref.get_cache_entry(num_words_per_block)) + + ref_statuses.append(ref_status) + + return cache, ref_statuses + + # Displays details for each address reference, including its hit/miss + # status + def display_addr_refs(self, refs, ref_statuses, table_width): + + table = Table( + num_cols=len(REF_COL_NAMES), width=table_width, alignment='right') + table.header[:] = REF_COL_NAMES + + for ref, ref_status in zip(refs, ref_statuses): + + if ref.tag is not None: + ref_tag = ref.tag + else: + ref_tag = 'n/a' + + if ref.index is not None: + ref_index = ref.index + else: + ref_index = 'n/a' + + if ref.offset is not None: + ref_offset = ref.offset + else: + ref_offset = 'n/a' + + # Display data for each address as a row in the table + table.rows.append(( + ref.word_addr, + BinaryAddress.prettify(ref.bin_addr, MIN_BITS_PER_GROUP), + BinaryAddress.prettify(ref_tag, MIN_BITS_PER_GROUP), + BinaryAddress.prettify(ref_index, MIN_BITS_PER_GROUP), + BinaryAddress.prettify(ref_offset, MIN_BITS_PER_GROUP), + ref_status)) + + print(table) + + # Displays the contents of the given cache as nicely-formatted table + def display_cache(self, cache, table_width): + + table = Table( + num_cols=len(cache), width=table_width, alignment='center') + table.title = 'Cache' + + cache_set_names = sorted(cache.keys()) + # A cache containing only one set is considered a fully associative + # cache + if len(cache) != 1: + # Display set names in table header if cache is not fully + # associative + table.header[:] = cache_set_names + + # Add to table the cache entries for each block + table.rows.append([]) + for index in cache_set_names: + blocks = cache[index] + table.rows[0].append(' '.join( + ','.join(map(str, entry['data'])) for entry in blocks)) + + print(table) + + # Run the entire cache simulation + def run_simulation(self, num_blocks_per_set, num_words_per_block, + cache_size, replacement_policy, num_addr_bits, + word_addrs): + + num_blocks = cache_size // num_words_per_block + num_sets = num_blocks // num_blocks_per_set + + # Ensure that the number of bits used to represent each address is + # always large enough to represent the largest address + num_addr_bits = max(num_addr_bits, int(math.log2(max(word_addrs))) + 1) + + num_offset_bits = int(math.log2(num_words_per_block)) + num_index_bits = int(math.log2(num_sets)) + num_tag_bits = num_addr_bits - num_index_bits - num_offset_bits + + refs = self.get_addr_refs( + word_addrs, num_addr_bits, + num_offset_bits, num_index_bits, num_tag_bits) + + cache, ref_statuses = self.read_refs_into_cache( + num_sets, num_blocks_per_set, num_index_bits, + num_words_per_block, replacement_policy, refs) + + # The character-width of all displayed tables + # Attempt to fit table to terminal width, otherwise use default of 80 + table_width = max((shutil.get_terminal_size( + (DEFAULT_TABLE_WIDTH, 20)).columns, DEFAULT_TABLE_WIDTH)) + + print() + self.display_addr_refs(refs, ref_statuses, table_width) + print() + self.display_cache(cache, table_width) + print() diff --git a/setup.py b/setup.py index b41bd33..4e92a64 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def get_long_description(): install_requires=[], entry_points={ 'console_scripts': [ - 'cache-simulator=cachesimulator.simulator:main' + 'cache-simulator=cachesimulator.__main__:main' ] } ) diff --git a/tests/test_simulator_display.py b/tests/test_simulator_display.py index 54310cd..efb29d7 100644 --- a/tests/test_simulator_display.py +++ b/tests/test_simulator_display.py @@ -5,7 +5,7 @@ import nose.tools as nose -import cachesimulator.simulator as sim +from cachesimulator.simulator import Simulator WORD_ADDRS = [43, 14, 253, 186] TABLE_WIDTH = 80 @@ -13,6 +13,7 @@ def test_display_addr_refs(): """should display table of address references""" + sim = Simulator() refs = sim.get_addr_refs( word_addrs=WORD_ADDRS, num_addr_bits=8, num_tag_bits=5, num_index_bits=2, num_offset_bits=1) @@ -38,6 +39,7 @@ def test_display_addr_refs(): def test_display_addr_refs_no_tag(): """should display n/a for tag when there are no tag bits""" + sim = Simulator() refs = sim.get_addr_refs( word_addrs=WORD_ADDRS, num_addr_bits=2, num_tag_bits=0, num_index_bits=1, num_offset_bits=1) @@ -53,6 +55,7 @@ def test_display_addr_refs_no_tag(): def test_display_addr_refs_no_index(): """should display n/a for index when there are no index bits""" + sim = Simulator() refs = sim.get_addr_refs( word_addrs=WORD_ADDRS, num_addr_bits=8, num_tag_bits=7, num_index_bits=0, num_offset_bits=1) @@ -68,6 +71,7 @@ def test_display_addr_refs_no_index(): def test_display_addr_refs_no_offset(): """should display n/a for offset when there are no offset bits""" + sim = Simulator() refs = sim.get_addr_refs( word_addrs=WORD_ADDRS, num_addr_bits=8, num_tag_bits=4, num_index_bits=4, num_offset_bits=0) @@ -83,6 +87,7 @@ def test_display_addr_refs_no_offset(): def test_display_cache(): """should display table for direct-mapped/set associative cache""" + sim = Simulator() out = io.StringIO() with contextlib.redirect_stdout(out): sim.display_cache({ @@ -115,6 +120,7 @@ def test_display_cache(): def test_display_cache_fully_assoc(): """should correctly display table for fully associative cache""" + sim = Simulator() out = io.StringIO() with contextlib.redirect_stdout(out): sim.display_cache({ diff --git a/tests/test_simulator_main.py b/tests/test_simulator_main.py index 0b4083a..db2d0db 100644 --- a/tests/test_simulator_main.py +++ b/tests/test_simulator_main.py @@ -6,17 +6,17 @@ import nose.tools as nose -import cachesimulator.simulator as sim +import cachesimulator.__main__ as main @patch('sys.argv', [ - sim.__file__, '--cache-size', '4', '--num-blocks-per-set', '1', + main.__file__, '--cache-size', '4', '--num-blocks-per-set', '1', '--num-words-per-block', '1', '--word-addrs', '0', '8', '0', '6', '8']) def test_main(): """main function should produce some output""" out = io.StringIO() with contextlib.redirect_stdout(out): - sim.main() + main.main() main_output = out.getvalue() nose.assert_regexp_matches(main_output, r'\bWordAddr\b') nose.assert_regexp_matches(main_output, r'\b0110\b') diff --git a/tests/test_simulator_refs.py b/tests/test_simulator_refs.py index ef31713..1999bc1 100644 --- a/tests/test_simulator_refs.py +++ b/tests/test_simulator_refs.py @@ -2,13 +2,14 @@ import nose.tools as nose -import cachesimulator.simulator as sim from cachesimulator.cache import Cache +from cachesimulator.simulator import Simulator def test_get_addr_refs(): """get_addr_refs should return correct reference data""" word_addrs = [3, 180, 44, 253] + sim = Simulator() refs = sim.get_addr_refs( word_addrs=word_addrs, num_addr_bits=8, num_tag_bits=4, num_index_bits=3, num_offset_bits=1) @@ -34,6 +35,7 @@ def get_hits(self, ref_statuses): def test_read_refs_into_cache_direct_mapped_lru(self): """read_refs_into_cache should work for direct-mapped LRU cache""" word_addrs = [0, 8, 0, 6, 8] + sim = Simulator() refs = sim.get_addr_refs( word_addrs=word_addrs, num_addr_bits=4, num_tag_bits=2, num_index_bits=2, num_offset_bits=0) @@ -54,6 +56,7 @@ def test_read_refs_into_cache_direct_mapped_lru(self): def test_read_refs_into_cache_set_associative_lru(self): """read_refs_into_cache should work for set associative LRU cache""" + sim = Simulator() refs = sim.get_addr_refs( word_addrs=TestReadRefs.WORD_ADDRS, num_addr_bits=8, num_tag_bits=5, num_index_bits=2, num_offset_bits=1) @@ -83,6 +86,7 @@ def test_read_refs_into_cache_set_associative_lru(self): def test_read_refs_into_cache_fully_associative_lru(self): """read_refs_into_cache should work for fully associative LRU cache""" + sim = Simulator() refs = sim.get_addr_refs( word_addrs=TestReadRefs.WORD_ADDRS, num_addr_bits=8, num_tag_bits=7, num_index_bits=0, num_offset_bits=1) @@ -101,6 +105,7 @@ def test_read_refs_into_cache_fully_associative_lru(self): def test_read_refs_into_cache_fully_associative_mru(self): """read_refs_into_cache should work for fully associative MRU cache""" + sim = Simulator() refs = sim.get_addr_refs( word_addrs=TestReadRefs.WORD_ADDRS, num_addr_bits=8, num_tag_bits=7, num_index_bits=0, num_offset_bits=1) From 46163d4053380a7ed019deb61ccc5d76d7f5ea92 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 11 Mar 2018 19:33:17 -0700 Subject: [PATCH 23/31] Indicate that Python 3.4 or later is required --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6d1a5f..8b60614 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This program simulates a processor cache for the MIPS instruction set architecture. It can simulate all three fundamental caching schemes: direct-mapped, *n*-way set associative, and fully associative. -The program must be run from the command line and requires Python 3 to run. Executing the program will run the simulation and print an ASCII table containing the details for each supplied word address, as well as the final contents of the cache. +The program must be run from the command line and requires Python 3.4+ to run. Executing the program will run the simulation and print an ASCII table containing the details for each supplied word address, as well as the final contents of the cache. To see example input and output, see `examples.txt`. From 7e6126424ff8750399eb1a3bf4b997e4934fad29 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 11 Mar 2018 19:34:34 -0700 Subject: [PATCH 24/31] Reflow README paragraphs --- README.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8b60614..45e4893 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,14 @@ [![Build Status](https://travis-ci.org/caleb531/cache-simulator.svg?branch=master)](https://travis-ci.org/caleb531/cache-simulator) [![Coverage Status](https://coveralls.io/repos/caleb531/cache-simulator/badge.svg?branch=master)](https://coveralls.io/r/caleb531/cache-simulator?branch=master) -This program simulates a processor cache for the MIPS instruction set architecture. It can simulate all three fundamental caching schemes: direct-mapped, *n*-way set associative, and fully associative. +This program simulates a processor cache for the MIPS instruction set +architecture. It can simulate all three fundamental caching schemes: +direct-mapped, *n*-way set associative, and fully associative. -The program must be run from the command line and requires Python 3.4+ to run. Executing the program will run the simulation and print an ASCII table containing the details for each supplied word address, as well as the final contents of the cache. +The program must be run from the command line and requires Python 3.4+ to run. +Executing the program will run the simulation and print an ASCII table +containing the details for each supplied word address, as well as the final +contents of the cache. To see example input and output, see `examples.txt`. @@ -30,13 +35,17 @@ The size of the cache in words (recall that one word is four bytes in MIPS). #### --word-addrs -One or more word addresses (separated by spaces), where each word address is a base-10 positive integer. +One or more word addresses (separated by spaces), where each word address is a +base-10 positive integer. ### Optional parameters #### --num-blocks-per-set -The program internally represents all cache schemes using a set associative cache. A value of `1` for this parameter (the default) implies a direct-mapped cache. A value other than `1` implies either a set associative *or* fully associative cache. +The program internally represents all cache schemes using a set associative +cache. A value of `1` for this parameter (the default) implies a direct-mapped +cache. A value other than `1` implies either a set associative *or* fully +associative cache. #### --num-words-per-block @@ -44,8 +53,12 @@ The number of words to store for each block in the cache; the default value is ` #### --num-addr-bits -The number of bits used to represent each given word address; this value is reflected in the *BinAddr* column in the reference table. If omitted, the default value is the number of bits needed to represent the largest of the given word addresses. +The number of bits used to represent each given word address; this value is +reflected in the *BinAddr* column in the reference table. If omitted, the +default value is the number of bits needed to represent the largest of the given +word addresses. #### --replacement-policy -The replacement policy to use for the cache. Accepted values are `lru` (Least Recently Used; the default) and `mru` (Most Recently Used). +The replacement policy to use for the cache. Accepted values are `lru` (Least +Recently Used; the default) and `mru` (Most Recently Used). From 7510a1893cfbeb3ec036a3aca1f225d4ccfe8477 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 11 Mar 2018 20:22:18 -0700 Subject: [PATCH 25/31] Move relevant simulator functions into Cache class --- cachesimulator/cache.py | 40 ++++++++++++++++++- cachesimulator/reference.py | 1 + cachesimulator/simulator.py | 66 ++++++------------------------- tests/test_simulator_display.py | 22 +++++++---- tests/test_simulator_hit.py | 6 +-- tests/test_simulator_refs.py | 42 +++++++++++--------- tests/test_simulator_set_block.py | 8 ++-- 7 files changed, 96 insertions(+), 89 deletions(-) diff --git a/cachesimulator/cache.py b/cachesimulator/cache.py index 0230cb9..1b80cbb 100644 --- a/cachesimulator/cache.py +++ b/cachesimulator/cache.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from cachesimulator.bin_addr import BinaryAddress +from cachesimulator.reference import ReferenceCacheStatus from cachesimulator.word_addr import WordAddress @@ -9,6 +10,10 @@ class Cache(dict): # Initializes the reference cache with a fixed number of sets def __init__(self, cache=None, num_sets=None, num_index_bits=0): + # A list of recently ordered addresses, ordered from least-recently + # used to most + self.recently_used_addrs = [] + if cache is not None: self.update(cache) else: @@ -17,6 +22,16 @@ def __init__(self, cache=None, num_sets=None, num_index_bits=0): word_addr=WordAddress(i), num_addr_bits=num_index_bits) self[index] = [] + # Every time we see an address, place it at the top of the + # list of recently-seen addresses + def mark_ref_as_last_seen(self, ref): + + # The index and tag (not the offset) uniquely identify each address + addr_id = (ref.index, ref.tag) + if addr_id in self.recently_used_addrs: + self.recently_used_addrs.remove(addr_id) + self.recently_used_addrs.append(addr_id) + # Returns True if a block at the given index and tag exists in the cache, # indicating a hit; returns False otherwise, indicating a miss def is_hit(self, addr_index, addr_tag): @@ -36,7 +51,7 @@ def is_hit(self, addr_index, addr_tag): return False # Adds the given entry to the cache at the given index - def set_block(self, recently_used_addrs, replacement_policy, + def set_block(self, replacement_policy, num_blocks_per_set, addr_index, new_entry): # Place all cache entries in a single set if cache is fully associative @@ -49,7 +64,9 @@ def set_block(self, recently_used_addrs, replacement_policy, # Iterate through the recently-used entries in reverse order for # MRU if replacement_policy == 'mru': - recently_used_addrs = reversed(recently_used_addrs) + recently_used_addrs = reversed(self.recently_used_addrs) + else: + recently_used_addrs = self.recently_used_addrs # Replace the first matching entry with the entry to add for recent_index, recent_tag in recently_used_addrs: for i, block in enumerate(blocks): @@ -59,3 +76,22 @@ def set_block(self, recently_used_addrs, replacement_policy, return else: blocks.append(new_entry) + + # Simulate the cache by reading the given address references into it + def read_refs(self, num_blocks_per_set, + num_words_per_block, replacement_policy, refs): + + for ref in refs: + self.mark_ref_as_last_seen(ref) + + # Record if the reference is already in the cache or not + if self.is_hit(ref.index, ref.tag): + # Give emphasis to hits in contrast to misses + ref.cache_status = ReferenceCacheStatus.hit + else: + ref.cache_status = ReferenceCacheStatus.miss + self.set_block( + replacement_policy=replacement_policy, + num_blocks_per_set=num_blocks_per_set, + addr_index=ref.index, + new_entry=ref.get_cache_entry(num_words_per_block)) diff --git a/cachesimulator/reference.py b/cachesimulator/reference.py index 9fdea4a..2b4d837 100644 --- a/cachesimulator/reference.py +++ b/cachesimulator/reference.py @@ -17,6 +17,7 @@ def __init__(self, word_addr, num_addr_bits, self.offset = self.bin_addr.get_offset(num_offset_bits) self.index = self.bin_addr.get_index(num_offset_bits, num_index_bits) self.tag = self.bin_addr.get_tag(num_tag_bits) + self.cache_status = ReferenceCacheStatus.miss # Return a lightweight entry to store in the cache def get_cache_entry(self, num_words_per_block): diff --git a/cachesimulator/simulator.py b/cachesimulator/simulator.py index 1ba3b24..191e92f 100755 --- a/cachesimulator/simulator.py +++ b/cachesimulator/simulator.py @@ -5,7 +5,7 @@ from cachesimulator.bin_addr import BinaryAddress from cachesimulator.cache import Cache -from cachesimulator.reference import Reference, ReferenceCacheStatus +from cachesimulator.reference import Reference from cachesimulator.table import Table # The names of all reference table columns @@ -18,12 +18,6 @@ class Simulator(object): - def __init__(self): - - # A list of recently ordered addresses, ordered from least-recently - # used to most - self.recently_used_addrs = [] - # Retrieves a list of address references for use by simulator def get_addr_refs(self, word_addrs, num_addr_bits, num_offset_bits, num_index_bits, num_tag_bits): @@ -32,55 +26,15 @@ def get_addr_refs(self, word_addrs, num_addr_bits, word_addr, num_addr_bits, num_offset_bits, num_index_bits, num_tag_bits) for word_addr in word_addrs] - # Every time we see an address, place it at the top of the - # list of recently-seen addresses - def mark_ref_as_last_seen(self, ref): - - # The index and tag (not the offset) uniquely identify each address - addr_id = (ref.index, ref.tag) - if addr_id in self.recently_used_addrs: - self.recently_used_addrs.remove(addr_id) - self.recently_used_addrs.append(addr_id) - - # Simulate the cache by reading the given address references into it - def read_refs_into_cache(self, num_sets, num_blocks_per_set, - num_index_bits, num_words_per_block, - replacement_policy, refs): - - cache = Cache( - num_sets=num_sets, - num_index_bits=num_index_bits) - ref_statuses = [] - - for ref in refs: - self.mark_ref_as_last_seen(ref) - - # Record if the reference is already in the cache or not - if cache.is_hit(ref.index, ref.tag): - # Give emphasis to hits in contrast to misses - ref_status = ReferenceCacheStatus.hit - else: - ref_status = ReferenceCacheStatus.miss - cache.set_block( - recently_used_addrs=self.recently_used_addrs, - replacement_policy=replacement_policy, - num_blocks_per_set=num_blocks_per_set, - addr_index=ref.index, - new_entry=ref.get_cache_entry(num_words_per_block)) - - ref_statuses.append(ref_status) - - return cache, ref_statuses - # Displays details for each address reference, including its hit/miss # status - def display_addr_refs(self, refs, ref_statuses, table_width): + def display_addr_refs(self, refs, table_width): table = Table( num_cols=len(REF_COL_NAMES), width=table_width, alignment='right') table.header[:] = REF_COL_NAMES - for ref, ref_status in zip(refs, ref_statuses): + for ref in refs: if ref.tag is not None: ref_tag = ref.tag @@ -104,7 +58,7 @@ def display_addr_refs(self, refs, ref_statuses, table_width): BinaryAddress.prettify(ref_tag, MIN_BITS_PER_GROUP), BinaryAddress.prettify(ref_index, MIN_BITS_PER_GROUP), BinaryAddress.prettify(ref_offset, MIN_BITS_PER_GROUP), - ref_status)) + ref.cache_status)) print(table) @@ -152,9 +106,13 @@ def run_simulation(self, num_blocks_per_set, num_words_per_block, word_addrs, num_addr_bits, num_offset_bits, num_index_bits, num_tag_bits) - cache, ref_statuses = self.read_refs_into_cache( - num_sets, num_blocks_per_set, num_index_bits, - num_words_per_block, replacement_policy, refs) + cache = Cache( + num_sets=num_sets, + num_index_bits=num_index_bits) + + cache.read_refs( + num_blocks_per_set, num_words_per_block, + replacement_policy, refs) # The character-width of all displayed tables # Attempt to fit table to terminal width, otherwise use default of 80 @@ -162,7 +120,7 @@ def run_simulation(self, num_blocks_per_set, num_words_per_block, (DEFAULT_TABLE_WIDTH, 20)).columns, DEFAULT_TABLE_WIDTH)) print() - self.display_addr_refs(refs, ref_statuses, table_width) + self.display_addr_refs(refs, table_width) print() self.display_cache(cache, table_width) print() diff --git a/tests/test_simulator_display.py b/tests/test_simulator_display.py index efb29d7..ce8fb7d 100644 --- a/tests/test_simulator_display.py +++ b/tests/test_simulator_display.py @@ -11,16 +11,22 @@ TABLE_WIDTH = 80 +def apply_cache_statuses_to_refs(cache_statuses, refs): + + for cache_status, ref in zip(cache_statuses, refs): + ref.cache_status = cache_status + + def test_display_addr_refs(): """should display table of address references""" sim = Simulator() refs = sim.get_addr_refs( word_addrs=WORD_ADDRS, num_addr_bits=8, num_tag_bits=5, num_index_bits=2, num_offset_bits=1) - ref_statuses = ['miss', 'miss', 'HIT', 'miss'] + apply_cache_statuses_to_refs(['miss', 'miss', 'HIT', 'miss'], refs) out = io.StringIO() with contextlib.redirect_stdout(out): - sim.display_addr_refs(refs, ref_statuses, table_width=TABLE_WIDTH) + sim.display_addr_refs(refs, table_width=TABLE_WIDTH) table_output = out.getvalue() num_cols = 6 col_width = TABLE_WIDTH // num_cols @@ -43,10 +49,10 @@ def test_display_addr_refs_no_tag(): refs = sim.get_addr_refs( word_addrs=WORD_ADDRS, num_addr_bits=2, num_tag_bits=0, num_index_bits=1, num_offset_bits=1) - ref_statuses = ['miss', 'miss', 'miss', 'miss'] + apply_cache_statuses_to_refs(['miss', 'miss', 'miss', 'miss'], refs) out = io.StringIO() with contextlib.redirect_stdout(out): - sim.display_addr_refs(refs, ref_statuses, table_width=TABLE_WIDTH) + sim.display_addr_refs(refs, table_width=TABLE_WIDTH) table_output = out.getvalue() nose.assert_regexp_matches( table_output, r'\s*{}\s*{}\s*{}'.format( @@ -59,10 +65,10 @@ def test_display_addr_refs_no_index(): refs = sim.get_addr_refs( word_addrs=WORD_ADDRS, num_addr_bits=8, num_tag_bits=7, num_index_bits=0, num_offset_bits=1) - ref_statuses = ['miss', 'miss', 'miss', 'miss'] + apply_cache_statuses_to_refs(['miss', 'miss', 'miss', 'miss'], refs) out = io.StringIO() with contextlib.redirect_stdout(out): - sim.display_addr_refs(refs, ref_statuses, table_width=TABLE_WIDTH) + sim.display_addr_refs(refs, table_width=TABLE_WIDTH) table_output = out.getvalue() nose.assert_regexp_matches( table_output, r'\s*{}\s*{}\s*{}'.format( @@ -75,10 +81,10 @@ def test_display_addr_refs_no_offset(): refs = sim.get_addr_refs( word_addrs=WORD_ADDRS, num_addr_bits=8, num_tag_bits=4, num_index_bits=4, num_offset_bits=0) - ref_statuses = ['miss'] * 12 + apply_cache_statuses_to_refs(['miss'] * 12, refs) out = io.StringIO() with contextlib.redirect_stdout(out): - sim.display_addr_refs(refs, ref_statuses, table_width=TABLE_WIDTH) + sim.display_addr_refs(refs, table_width=TABLE_WIDTH) table_output = out.getvalue() nose.assert_regexp_matches( table_output, r'\s*{}\s*{}\s*{}'.format( diff --git a/tests/test_simulator_hit.py b/tests/test_simulator_hit.py index 55bf822..f2f65b7 100644 --- a/tests/test_simulator_hit.py +++ b/tests/test_simulator_hit.py @@ -2,14 +2,14 @@ import nose.tools as nose -import cachesimulator.simulator as sim from cachesimulator.cache import Cache +from cachesimulator.reference import ReferenceCacheStatus def test_ref_status_str(): """cache status enum members should display correct string values""" - nose.assert_equal(str(sim.ReferenceCacheStatus.hit), 'HIT') - nose.assert_equal(str(sim.ReferenceCacheStatus.miss), 'miss') + nose.assert_equal(str(ReferenceCacheStatus.hit), 'HIT') + nose.assert_equal(str(ReferenceCacheStatus.miss), 'miss') class TestIsHit(object): diff --git a/tests/test_simulator_refs.py b/tests/test_simulator_refs.py index 1999bc1..e2721c4 100644 --- a/tests/test_simulator_refs.py +++ b/tests/test_simulator_refs.py @@ -3,6 +3,7 @@ import nose.tools as nose from cachesimulator.cache import Cache +from cachesimulator.reference import ReferenceCacheStatus from cachesimulator.simulator import Simulator @@ -27,10 +28,11 @@ class TestReadRefs(object): WORD_ADDRS = [3, 180, 43, 2, 191, 88, 190, 14, 181, 44, 186, 253] - def get_hits(self, ref_statuses): + def get_hits(self, refs): """retrieves all indices where hits occur in a list of ref statuses""" return { - i for i, status in enumerate(ref_statuses) if status.value == 1} + i for i, ref in enumerate(refs) + if ref.cache_status == ReferenceCacheStatus.hit} def test_read_refs_into_cache_direct_mapped_lru(self): """read_refs_into_cache should work for direct-mapped LRU cache""" @@ -39,9 +41,10 @@ def test_read_refs_into_cache_direct_mapped_lru(self): refs = sim.get_addr_refs( word_addrs=word_addrs, num_addr_bits=4, num_tag_bits=2, num_index_bits=2, num_offset_bits=0) - cache, ref_statuses = sim.read_refs_into_cache( - refs=refs, num_sets=4, num_blocks_per_set=1, - num_words_per_block=1, num_index_bits=2, replacement_policy='lru') + cache = Cache(num_sets=4, num_index_bits=2) + cache.read_refs( + refs=refs, num_blocks_per_set=1, + num_words_per_block=1, replacement_policy='lru') nose.assert_equal(cache, { '00': [ {'tag': '10', 'data': [8]} @@ -52,7 +55,7 @@ def test_read_refs_into_cache_direct_mapped_lru(self): ], '11': [] }) - nose.assert_equal(self.get_hits(ref_statuses), set()) + nose.assert_equal(self.get_hits(refs), set()) def test_read_refs_into_cache_set_associative_lru(self): """read_refs_into_cache should work for set associative LRU cache""" @@ -60,9 +63,10 @@ def test_read_refs_into_cache_set_associative_lru(self): refs = sim.get_addr_refs( word_addrs=TestReadRefs.WORD_ADDRS, num_addr_bits=8, num_tag_bits=5, num_index_bits=2, num_offset_bits=1) - cache, ref_statuses = sim.read_refs_into_cache( - refs=refs, num_sets=4, num_blocks_per_set=3, - num_words_per_block=2, num_index_bits=2, replacement_policy='lru') + cache = Cache(num_sets=4, num_index_bits=2) + cache.read_refs( + refs=refs, num_blocks_per_set=3, + num_words_per_block=2, replacement_policy='lru') nose.assert_equal(cache, { '00': [ {'tag': '01011', 'data': [88, 89]} @@ -82,7 +86,7 @@ def test_read_refs_into_cache_set_associative_lru(self): {'tag': '00001', 'data': [14, 15]}, ] }) - nose.assert_equal(self.get_hits(ref_statuses), {3, 6, 8}) + nose.assert_equal(self.get_hits(refs), {3, 6, 8}) def test_read_refs_into_cache_fully_associative_lru(self): """read_refs_into_cache should work for fully associative LRU cache""" @@ -90,9 +94,10 @@ def test_read_refs_into_cache_fully_associative_lru(self): refs = sim.get_addr_refs( word_addrs=TestReadRefs.WORD_ADDRS, num_addr_bits=8, num_tag_bits=7, num_index_bits=0, num_offset_bits=1) - cache, ref_statuses = sim.read_refs_into_cache( - refs=refs, num_sets=1, num_blocks_per_set=4, - num_words_per_block=2, num_index_bits=0, replacement_policy='lru') + cache = Cache(num_sets=1, num_index_bits=0) + cache.read_refs( + refs=refs, num_blocks_per_set=4, + num_words_per_block=2, replacement_policy='lru') nose.assert_equal(cache, { '0': [ {'tag': '1011010', 'data': [180, 181]}, @@ -101,7 +106,7 @@ def test_read_refs_into_cache_fully_associative_lru(self): {'tag': '1011101', 'data': [186, 187]} ] }) - nose.assert_equal(self.get_hits(ref_statuses), {3, 6}) + nose.assert_equal(self.get_hits(refs), {3, 6}) def test_read_refs_into_cache_fully_associative_mru(self): """read_refs_into_cache should work for fully associative MRU cache""" @@ -109,9 +114,10 @@ def test_read_refs_into_cache_fully_associative_mru(self): refs = sim.get_addr_refs( word_addrs=TestReadRefs.WORD_ADDRS, num_addr_bits=8, num_tag_bits=7, num_index_bits=0, num_offset_bits=1) - cache, ref_statuses = sim.read_refs_into_cache( - refs=refs, num_sets=1, num_blocks_per_set=4, - num_words_per_block=2, num_index_bits=0, replacement_policy='mru') + cache = Cache(num_sets=1, num_index_bits=0) + cache.read_refs( + refs=refs, num_blocks_per_set=4, + num_words_per_block=2, replacement_policy='mru') nose.assert_equal(cache, Cache({ '0': [ {'tag': '0000001', 'data': [2, 3]}, @@ -120,4 +126,4 @@ def test_read_refs_into_cache_fully_associative_mru(self): {'tag': '0000111', 'data': [14, 15]} ] })) - nose.assert_equal(self.get_hits(ref_statuses), {3, 8}) + nose.assert_equal(self.get_hits(refs), {3, 8}) diff --git a/tests/test_simulator_set_block.py b/tests/test_simulator_set_block.py index 2befaf4..04d6f3b 100644 --- a/tests/test_simulator_set_block.py +++ b/tests/test_simulator_set_block.py @@ -30,8 +30,8 @@ def test_empty_set(self): """set_block should add new block if index set is empty""" self.reset() self.cache['010'][:] = [] + self.cache.recently_used_addrs = [] self.cache.set_block( - recently_used_addrs=[], replacement_policy='lru', num_blocks_per_set=4, addr_index='010', @@ -43,8 +43,8 @@ def test_empty_set(self): def test_lru_replacement(self): """set_block should perform LRU replacement as needed""" self.reset() + self.cache.recently_used_addrs = self.recently_used_addrs self.cache.set_block( - recently_used_addrs=self.recently_used_addrs, replacement_policy='lru', num_blocks_per_set=4, addr_index='010', @@ -61,8 +61,8 @@ def test_lru_replacement(self): def test_mru_replacement(self): """set_block should optionally perform MRU replacement as needed""" self.reset() + self.cache.recently_used_addrs = self.recently_used_addrs self.cache.set_block( - recently_used_addrs=self.recently_used_addrs, replacement_policy='mru', num_blocks_per_set=4, addr_index='010', @@ -80,8 +80,8 @@ def test_no_replacement(self): """set_block should not perform replacement if there are no recents""" self.reset() original_cache = copy.deepcopy(self.cache) + self.cache.recently_used_addrs = [] self.cache.set_block( - recently_used_addrs=[], replacement_policy='lru', num_blocks_per_set=4, addr_index='010', From bb0993acc92d8c0bbd7ffbf692c3f106eba0344e Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 18 Mar 2018 15:47:04 -0700 Subject: [PATCH 26/31] Eliminate bare exception --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4e92a64..9647464 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ def get_long_description(): # Use pandoc to create reStructuredText README if possible import pypandoc return pypandoc.convert('README.md', 'rst') - except: + except Exception: return None From d98df840577dff969ea706fb0858bbfeb386643a Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 18 Mar 2018 18:30:11 -0700 Subject: [PATCH 27/31] Remove executable flag from simulator module It was the only Python script that was previously marked as executable. --- cachesimulator/simulator.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 cachesimulator/simulator.py diff --git a/cachesimulator/simulator.py b/cachesimulator/simulator.py old mode 100755 new mode 100644 From 7db8e5e995040194804523f0966c8f64f9edf771 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 18 Mar 2018 18:44:49 -0700 Subject: [PATCH 28/31] Display friendly string representations for Reference --- cachesimulator/reference.py | 6 ++++++ tests/test_simulator_refs.py | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/cachesimulator/reference.py b/cachesimulator/reference.py index 2b4d837..6f99147 100644 --- a/cachesimulator/reference.py +++ b/cachesimulator/reference.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from collections import OrderedDict from enum import Enum from cachesimulator.bin_addr import BinaryAddress @@ -19,6 +20,11 @@ def __init__(self, word_addr, num_addr_bits, self.tag = self.bin_addr.get_tag(num_tag_bits) self.cache_status = ReferenceCacheStatus.miss + def __str__(self): + return str(OrderedDict(sorted(self.__dict__.items()))) + + __repr__ = __str__ + # Return a lightweight entry to store in the cache def get_cache_entry(self, num_words_per_block): return { diff --git a/tests/test_simulator_refs.py b/tests/test_simulator_refs.py index e2721c4..c710002 100644 --- a/tests/test_simulator_refs.py +++ b/tests/test_simulator_refs.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 +from collections import OrderedDict + import nose.tools as nose from cachesimulator.cache import Cache -from cachesimulator.reference import ReferenceCacheStatus +from cachesimulator.reference import Reference, ReferenceCacheStatus from cachesimulator.simulator import Simulator @@ -127,3 +129,10 @@ def test_read_refs_into_cache_fully_associative_mru(self): ] })) nose.assert_equal(self.get_hits(refs), {3, 8}) + + +def test_get_ref_str(): + """should return string representation of Reference""" + ref = Reference(word_addr=180, num_addr_bits=8, + num_tag_bits=4, num_index_bits=3, num_offset_bits=1) + nose.assert_equal(str(ref), str(OrderedDict(sorted(ref.__dict__.items())))) From f07038d4736cde622fc116b82963829ba3bd77dc Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 18 Mar 2018 18:45:37 -0700 Subject: [PATCH 29/31] Add __repr__ support to ReferenceCacheStatus --- cachesimulator/reference.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cachesimulator/reference.py b/cachesimulator/reference.py index 6f99147..12cd31b 100644 --- a/cachesimulator/reference.py +++ b/cachesimulator/reference.py @@ -46,3 +46,5 @@ def __str__(self): return 'HIT' else: return 'miss' + + __repr__ = __str__ From d2104a7a577d8755a0c27c9d1b74afcffc03af62 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 18 Mar 2018 21:25:32 -0700 Subject: [PATCH 30/31] Set the default Reference cache status to None This requires casting the table row values to strings because a non-string like None cannot be passed to str.format() (in the event a code error leaves the cache status unset). --- cachesimulator/reference.py | 2 +- cachesimulator/table.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cachesimulator/reference.py b/cachesimulator/reference.py index 12cd31b..f5bdab2 100644 --- a/cachesimulator/reference.py +++ b/cachesimulator/reference.py @@ -18,7 +18,7 @@ def __init__(self, word_addr, num_addr_bits, self.offset = self.bin_addr.get_offset(num_offset_bits) self.index = self.bin_addr.get_index(num_offset_bits, num_index_bits) self.tag = self.bin_addr.get_tag(num_tag_bits) - self.cache_status = ReferenceCacheStatus.miss + self.cache_status = None def __str__(self): return str(OrderedDict(sorted(self.__dict__.items()))) diff --git a/cachesimulator/table.py b/cachesimulator/table.py index 3569b45..2992074 100644 --- a/cachesimulator/table.py +++ b/cachesimulator/table.py @@ -43,6 +43,6 @@ def __str__(self): table_strs.append(self.get_separator()) for row in self.rows: - table_strs.append(cell_format_str.format(*row)) + table_strs.append(cell_format_str.format(*map(str, row))) return '\n'.join(table_strs) From 34e543985849841d8917c0a545a47b9d9c223f56 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Sun, 18 Mar 2018 21:35:40 -0700 Subject: [PATCH 31/31] Use None instead of an arbitrary terminal height --- cachesimulator/simulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cachesimulator/simulator.py b/cachesimulator/simulator.py index 191e92f..20c8059 100644 --- a/cachesimulator/simulator.py +++ b/cachesimulator/simulator.py @@ -117,7 +117,7 @@ def run_simulation(self, num_blocks_per_set, num_words_per_block, # The character-width of all displayed tables # Attempt to fit table to terminal width, otherwise use default of 80 table_width = max((shutil.get_terminal_size( - (DEFAULT_TABLE_WIDTH, 20)).columns, DEFAULT_TABLE_WIDTH)) + (DEFAULT_TABLE_WIDTH, None)).columns, DEFAULT_TABLE_WIDTH)) print() self.display_addr_refs(refs, table_width)