diff --git a/main.py b/main.py index b89cb9ddf53504a2053dfe1304ae742f52e10dee..57ec0683727c9934b09a55ecf2862b0268a42d17 100644 --- a/main.py +++ b/main.py @@ -22,221 +22,218 @@ import argparse, math, re # User Imports. -def main(set_identifier_bit_count, line_count, block_identifier_bit_count, file_name, verbose): - """ - Program main. - """ - print('\n') - print('main():') - print(' set_identifier_bit_count: {0}'.format(set_identifier_bit_count)) - print(' line_count: {0}'.format(line_count)) - print(' block_identifier_bit_count: {0}'.format(block_identifier_bit_count)) - print(' file_name: {0}'.format(file_name)) - print(' verbose: {0}'.format(verbose)) - print('') - - # Calculate total sizes of cache structures. - s = set_identifier_bit_count - S = set_count = int(math.pow(2, s)) - E = line_count - e = line_identifier_bit_count = math.log2(E) - b = block_identifier_bit_count - B = block_size = int(math.pow(2, b)) - m = address_identifier_bit_count = E * S - # M = total_bytes = int(math.pow(2, m)) - M = int(math.pow(2, 64)) - C = capacity = S * E * B - tag_identifier_bit_count = m - (b + s) - - # Create cache object. - cache = [] - for set in range(set_count): - cache.append([]) - - # Create lines in set. - for line in range(line_count): - cache[len(cache) - 1].append({ - 'valid': False, - 'tag': 0, - 'block': [], - }) - - # print('Address Bit Count: {0}'.format(address_identifier_bit_count)) - # print('B: {0}'.format(B)) - # print('S: {0}'.format(S)) - # print('E: {0}'.format(E)) - # print('Tag Bits: {0}'.format(tag_identifier_bit_count)) - print_cache(cache) - print('\n\n') - - run_simulation(cache, file_name, M, B, S) - - -def run_simulation(cache, file_name, M, B, S): - """ - - :param cache: - :return: - """ - hits = 0 - misses = 0 - evictions = 0 - - # Open file to read in values. - with open(file_name) as file: - # Read in line. - for line in file: - # print('Line: {0}'.format(line)) - cache, hits, misses, evictions = handle_line(cache, line, M, B, S, hits, misses, evictions) - # print_cache(cache) - - # print('M: {0} As Bits: {1:b}'.format(M, M - 1)) - # print('B: {0} As Bits: {1:b}'.format(B, B - 1)) - # print('S: {0} As Bits: {1:b}'.format(S, S - 1)) - - print('hits:{0} misses:{1} evictions:{2}'.format(hits, misses, evictions)) - - -def handle_line(cache, line, M, B, S, hits, misses, evictions, print_results=True): - """ - - :param cache: - :param line: - :return: - """ - split_line = re.split(r'[ ,\n]', line) - # print('split_line: {0}'.format(split_line)) - - # Check if instruction load. If so, we can skip line. - if split_line[0] == 'I': - # Found instruction load. Returning. - return (cache, hits, misses, evictions) - else: - line = line.strip() - conflict_miss = -1 - # print('\n') - # print('handle_line():') - # print(' line: {0}'.format(line)) - # print(' M: {0}'.format(M)) - # print(' B: {0}'.format(B)) - # print(' S: {0}'.format(S)) - # print('') - - # Not an instruction load. Keep processing. Start by getting address. - address = int(split_line[2]) % M - value_size = int(split_line[3]) - - # Get mask values. - full_mask = M - 1 - block_mask = B - 1 - block_offset = int(math.log2(B)) - set_mask = ((S << block_offset) - 1) ^ block_mask - set_offset = int(math.log2(S)) - tag_mask = (M - 1) ^ set_mask ^ block_mask - - # print('') - # print('Address: {0} As Bits: {0:b}'.format(address)) - # print('full_mask: {0} As Bits: {0:b}'.format(full_mask)) - # print('block_mask: {0} As Bits: {0:b}'.format(block_mask)) - # print('set_mask: {0} As Bits: {0:b}'.format(set_mask)) - # print('tag_mask: {0} As Bits: {0:b}'.format(tag_mask)) - # print('') - - # Use masks to get address chunks. - block = address & block_mask - set = (address & set_mask) >> block_offset - tag = (address & tag_mask) >> (block_offset + set_offset) - - # print('block: {0} As Bits: {0:b}'.format(block)) - # print('set: {0} As Bits: {0:b}'.format(set)) - # print('tag: {0} As Bits: {0:b}'.format(tag)) - - # We have our values. Now compare against the all entries in the cache. - for index in range(len(cache[set])): - # Check if match. - if cache[set][index]['valid'] == True and cache[set][index]['tag'] == tag: - # Match found. - if print_results: - print('{0} hit'.format(line)) - - hits += 1 - return (cache, hits, misses, evictions) - else: - # Not a match. Examine why. - if cache[set][index]['valid'] == False: - # Cache location was not set. Cold miss. We can just set and return. - if print_results: - print('{0} miss'.format(line)) - - misses += 1 - cache[set][index]['valid'] = True - cache[set][index]['tag'] = tag - - # Set block values that are "loaded" into memory. - lower_offset = int(address / B) * B - upper_offset = (int(address / B) + 1) * B - value_offset = address + value_size - cache[set][index]['block'] = [] - for i in range(B): - cache[set][index]['block'].append(lower_offset + i) - - # Check if size sets address outside of current cache block. - if value_offset >= upper_offset: - # Size sets address outside of current cache block. Update additional blocks as needed. - # Start by getting our new size. - new_size = value_offset - upper_offset - if new_size < 0: - new_size = 0 - - # Pass our new line data. - new_line = ' L {0},{1}'.format(upper_offset, new_size) - cache, null, null, null = handle_line(cache, new_line, M, B, S, 0, 0, 0, False) - - return (cache, hits, misses, evictions) - - - # If we made it this far, cache locations were set, but tags did not match. Conflict miss. - if print_results: - print('{0} miss eviction'.format(line)) - - misses += 1 - evictions += 1 - cache[set][0]['tag'] = tag - - # Set block values that are "loaded" into memory. - lower_offset = int(address / B) * B - upper_offset = (int(address / B) + 1) * B - value_offset = address + value_size - cache[set][0]['block'] = [] - for i in range(B): - cache[set][0]['block'].append(lower_offset + i) - - # Check if size sets address outside of current cache block. - if value_offset >= upper_offset: - # Size sets address outside of current cache block. Update additional blocks as needed. - # Start by getting our new size. - new_size = value_offset - upper_offset - if new_size < 0: - new_size = 0 - - # Pass our new line data. - new_line = ' L {0},{1}'.format(upper_offset, new_size) - cache, null, null, null = handle_line(cache, new_line, M, B, S, 0, 0, 0, False) - - return (cache, hits, misses, evictions) - - -def print_cache(cache): - """ - Prints simulated cache in an easier to read format. - :param cache: Simulated cache to print. - """ - print('\nCache:') - for set_index in range(len(cache)): - print(' Set {0}:'.format(set_index, cache[set_index])) - for line_index in range(len(cache[set_index])): - print(' Line {0}: {1}'.format(line_index, cache[set_index][line_index])) - print('') +class CacheSimulator(): + def __init__(self): + """ + Class initialization. + """ + self.s = None # Number of bits to identify set. + self.S = None # Number of sets in cache. + self.e = None # Number of bits to identify line. + self.E = None # Number of lines per set. + self.b = None # Number of bits to identify block byte. + self.B = None # Number of bytes in a block. + self.m = None # Total number of identifying bits in address. + self.M = None # Total addresses in cache. + + self.cache = None + self.hits = None + self.misses = None + self.evictions = None + self.verbose = False + + def main(self, s, E, b, file_name, verbose): + """ + Program main. + """ + print('\n') + print('main():') + print(' s: {0}'.format(s)) + print(' E: {0}'.format(E)) + print(' b: {0}'.format(b)) + print(' file_name: {0}'.format(file_name)) + print(' verbose: {0}'.format(verbose)) + print('') + + if verbose: + self.verbose = True + + # Calculate total sizes of cache structures. + self.s = s + self.S = int(math.pow(2, self.s)) + self.E = E + self.e = math.log2(self.E) + self.b = b + self.B = int(math.pow(2, self.b)) + self.m = self.E * self.S + # M = total_bytes = int(math.pow(2, m)) + self.M = int(math.pow(2, 64)) + + # Create cache object. + self.cache = [] + for set in range(self.S): + self.cache.append([]) + + # Create lines in set. + for line in range(self.E): + self.cache[len(self.cache) - 1].append({ + 'valid': False, + 'tag': 0, + 'block': [], + }) + + # Display initial cache. + self.print_cache() + print('\n\n') + + # Run simulation on cache, using file. + self.run_simulation(file_name) + + + def run_simulation(self, file_name): + """ + Runs full simulation of cache, using given input file. + :param file_name: Name of file to read from. + """ + self.hits = 0 + self.misses = 0 + self.evictions = 0 + + # Open file to read in values. + with open(file_name) as file: + # Read in line. + for line in file: + self.handle_line(line) + + print('hits:{0} misses:{1} evictions:{2}'.format(self.hits, self.misses, self.evictions)) + + def handle_line(self, line, main_cache_line=True): + """ + Handles cache simulation for given line instruction. + :param line: Line to handle for. + :param main_cache_line: Bool to indicate if this is an initial cache line read in or a subquery from one. + Note that only initial cache line read ins will print or update missed/skipped/evicted variables. + """ + split_line = re.split(r'[ ,\n]', line) + + # Check if instruction load. If so, we can skip line. + if split_line[0] == 'I': + # Found instruction load. Returning. + return + else: + line = line.strip() + + # Not an instruction load. Keep processing. Start by getting address. + address = int(split_line[2]) % self.M + value_size = int(split_line[3]) + + # Get mask values. + block_mask = self.B - 1 + block_offset = self.b + set_mask = ((self.S << block_offset) - 1) ^ block_mask + set_offset = self.s + tag_mask = (self.M - 1) ^ set_mask ^ block_mask + + # Use masks to get address chunks. + block = address & block_mask + set = (address & set_mask) >> block_offset + tag = (address & tag_mask) >> (block_offset + set_offset) + + # We have our values. Now compare against the all entries in the cache. + for index in range(len(self.cache[set])): + # Check if match. + if self.cache[set][index]['valid'] == True and self.cache[set][index]['tag'] == tag: + # Match found. + if main_cache_line: + # Is main cache file line. Update hits/misses/evictions. + self.hits += 1 + + # Print if verbose flag set. + if self.verbose: + print('{0} hit'.format(line)) + + return + else: + # Not a match. Examine why. + if self.cache[set][index]['valid'] == False: + # Cache location was not set. Cold miss. We can just set and return. + if main_cache_line: + # Is main cache file line. Update hits/misses/evictions. + self.misses += 1 + + # Print if verbose flag set. + if self.verbose: + print('{0} miss'.format(line)) + + self.cache[set][index]['valid'] = True + self.cache[set][index]['tag'] = tag + + # Set block values that are "loaded" into memory. + lower_offset = int(address / self.B) * self.B + upper_offset = (int(address / self.B) + 1) * self.B + value_offset = address + value_size + self.cache[set][index]['block'] = [] + for i in range(self.B): + self.cache[set][index]['block'].append(lower_offset + i) + + # Check if size sets address outside of current cache block. + if value_offset >= upper_offset: + # Size sets address outside of current cache block. Update additional blocks as needed. + # Start by getting our new size. + new_size = value_offset - upper_offset + if new_size < 0: + new_size = 0 + + # Pass our new line data. + new_line = ' L {0},{1}'.format(upper_offset, new_size) + self.handle_line(new_line, main_cache_line=False) + + return + + # If we made it this far, cache locations were set, but tags did not match. Conflict miss. + if main_cache_line: + # Is main cache file line. Update hits/misses/evictions. + self.misses += 1 + self.evictions += 1 + + # Print if verbose flag set. + if self.verbose: + print('{0} miss eviction'.format(line)) + + self.cache[set][0]['tag'] = tag + + # Set block values that are "loaded" into memory. + lower_offset = int(address / self.B) * self.B + upper_offset = (int(address / self.B) + 1) * self.B + value_offset = address + value_size + self.cache[set][0]['block'] = [] + for i in range(self.B): + self.cache[set][0]['block'].append(lower_offset + i) + + # Check if size sets address outside of current cache block. + if value_offset >= upper_offset: + # Size sets address outside of current cache block. Update additional blocks as needed. + # Start by getting our new size. + new_size = value_offset - upper_offset + if new_size < 0: + new_size = 0 + + # Pass our new line data. + new_line = ' L {0},{1}'.format(upper_offset, new_size) + self.handle_line(new_line, main_cache_line=False) + + return + + def print_cache(self): + """ + Prints simulated cache in an easier to read format. + """ + print('\nCache:') + for set_index in range(len(self.cache)): + print(' Set {0}:'.format(set_index, self.cache[set_index])) + for line_index in range(len(self.cache[set_index])): + print(' Line {0}: {1}'.format(line_index, self.cache[set_index][line_index])) + print('') if __name__ == '__main__': @@ -252,6 +249,6 @@ if __name__ == '__main__': args = parser.parse_args() # If args parsed properly, run program main. - main(int(args.set_count), int(args.line_count), int(args.block_size), args.file_name, args.verbose) + CacheSimulator().main(int(args.set_count), int(args.line_count), int(args.block_size), args.file_name, args.verbose) print('Terminating program.')