class Cache: """ This class represents a cache component. """ def __init__(self, cache_size, block_size, debug): """ Initialize the cache component. """ self.debug_info = debug if self.debug_info is True: print("[Cache] initializing...") ### Initialization code ### self.cache_size = cache_size self.block_size = block_size self.num_of_blocks_used = 0 self.max_number_of_blocks = int(self.cache_size / self.block_size) self.cache = [Block()] * int(self.cache_size / self.block_size) self.bus = Bus(self.debug_info) ########################### #### Performance Stats #### self.hits = 0 self.misses = 0 self.replacements = 0 ########################### if self.debug_info is True: print("[Cache] finished initializing...") print("[Cache] Max number of blocks '", self.max_number_of_blocks, "'...") # return true if full, false otherwise def is_full(self): """ Return true if cache is full, false otherwise. """ return self.num_of_blocks_used == self.max_number_of_blocks # LRU , return least recently used block's position def LRU(self): """ Return the oldest, least recently used block. """ if self.debug_info is True: print("[Cache] Looking for least recently used block...") oldest_time = 0 oldest_block_number = None for index, block in enumerate(self.cache): if block.timer >= oldest_time: oldest_time = block.timer oldest_block_number = index if self.debug_info is True: print("[Cache] LRU block found...") print("[...] block data : ", self.cache[oldest_block_number].data) print("[...] block timer: ", self.cache[oldest_block_number].timer) return oldest_block_number # Least Recently Used algorithms # insert, insert data into a block in position of previous LRU def insert(self, data): """ Insert a new block in the LRU block's position. """ if self.debug_info is True: print("\n[Cache] inserting block...") print("[...] Data: ", data) print("[...] Current Cache table: ") self.print_cache() if self.is_full() is True: # Swap blocks if full print("[...] Cache full...") lru_block = self.LRU() if self.debug_info is True: print("[Cache] LRU block to be replaced: ") print("[...] data: ", self.cache[lru_block].data) print("[...] timer: ", self.cache[lru_block].timer) self.cache[lru_block] = Block() self.cache[lru_block].valid = 1 self.cache[lru_block].data = data self.print_cache() else: # Otherwise, place into an open spot print("[...] Cache has empty slots...") position = self.find_empty_spot() self.cache[position] = Block() self.cache[position].data = data self.cache[position].valid = 1 self.num_of_blocks_used = self.num_of_blocks_used + 1 def find_empty_spot(self): """ Find an empty spot in cache. Return index. """ for index, block in enumerate(self.cache): if block.valid == 0: return index return 0 def find_block(self, address): """ Returns "HIT" or "MISS" depending on if data is present. """ if self.debug_info is True: print("[Cache] looking for block with address '" + address + "'...") for block in self.cache: if block.data[0] == address: return block.data[1] # If this point reached, return Miss return "MISS" def look_for_block_addr_in_tlb(self, address, callee, callee_name): """ If data was missing, and find and store. """ if self.debug_info is True: print("[Cache] Looking for new block in TLB...") block_address = self.bus.communicate( "cache", callee, callee_name, "TLB, physical address of virtual", address) return block_address def retrieve_block(self, address, callee, callee_name): """ Use bus to get block with specified address. Return block. """ if self.debug_info is True: print("[Cache] Retrieving missing block...") block = self.bus.communicate("cache", callee, callee_name, "memory, give block", address) self.insert([address, block]) def update_timer(self): """ Update the timers for all blocks in memory, increment by 1. """ for block in self.cache: block.timer = block.timer + 1 def print_stats(self): """ Print the hit ratio, miss ratio, and replacement ratios. """ print("[Cache] Printing cache statistics:") total = self.hits + self.misses + self.replacements # Avoid dividing by 0 if total == 0: total = 1 print("[...] hit ratio: ", (self.hits / (total))) print("[...] miss ratio: ", (self.misses / (total))) print("[...] replacement ratio: ", (self.replacements / (total))) def print_cache(self): """ Print the contents of the cache. """ counter = 0 print("[Cache] Printing contents of cache:") print("[...] Set | B1 timer | B1 data\t| B2 timer | B2 data\t") for i in range(0, len(self.cache) - 1, 2): block1 = self.cache[i] block2 = self.cache[i + 1] print("[...] ", counter, " | ", block1.timer, " | ", block1.data, " | ", block2.timer, " | ", block2.data) counter = counter + 1
class Memory: """ This class handles the activity of the memory and virtual memory component. """ def __init__(self, memory_size, virtual_memory_size, debug): self.debug_info = debug if self.debug_info is True: print("[Memory] initializing...") ### Initialization code ### self.virtual_memory = [None] * virtual_memory_size self.memory = [None] * memory_size self.page_table_register = None self.bus = Bus(debug) self.memory_size = memory_size self.number_of_pages_loaded = 0 self.memory_used = 0 ### Virtual Memory ### # Assuming size of memory is self.stack_address_used = 0x8000 # decrease as memory is used, stack # grows up self.const_stack_start = 0x8000 self.heap_address_used = 0 # increase as memory is used self.const_heap_start = 0x4000 self.bss_address_used = 0 # increase as memory is used self.const_bss_start = 0x2000 self.text_address_used = 0 # increase as memory is used self.const_text_start = 0x1000 ########################### if self.debug_info is True: print("[Memory] finished initializing...") def load_initial_pages_of_program(self, callee, callee_name): """ Start from 'main:' label and load all subsequent pages into memory. """ self.page_table_register = self.bus.communicate("memory", callee, callee_name, "disk, initial page number", "") page_size = self.bus.communicate("memory", callee, callee_name, "disk, send page size", "") while (self.memory_size - self.memory_used) >= page_size: page = self.bus.communicate("memory", callee, callee_name, "disk, send page", str(self.page_table_register)) self.store_page(page) self.page_table_register = self.page_table_register + 1 # Place page in open spot in memory def store_page(self, page): """ Store given page in memory. """ if self.debug_info is True: print("[Memory] Attempting to store disk page ", page.page_number, "...") if (self.memory_size - self.memory_used) < page.page_size: if self.debug_info is True: print("[Memory] Memory (Text) full, replacing pages...") print("[ERROR] Not implemented yet.") #replace_page(page) else: # store page, instruction by instruction counter = self.memory_used for instruction in page.instructions: self.memory[counter] = instruction self.memory[counter+1] = "." # These are placeholders used to simulate self.memory[counter+2] = "." # byte addressing, ... self.memory[counter+3] = "." # ... counter = counter + 4 self.memory_used = self.memory_used + 4 def replace_page(self, page): """ Replace random page in memory with given page. """ if self.debug_info is True: print("[Memory] Replacing memory page randomly with page", page.page_number, "...") # memory_size/ page_size = number of page positions possible_page_positions = self.memory_size / page.page_size - 1 picked_position = randint(0, possible_page_positions) counter = 0 + (page.page_size * picked_position) for instruction in page.instructions: self.memory[counter] = instruction counter = counter + 4 # update_virtual_memory(page.page_number) def print_memory_page_table(self): """ Print the contents of memory. """ if self.debug_info is True: print("\n[Memory] Printing contents of memory...") unallocated = "...unallocated..." for index, instruction in enumerate(self.memory): if instruction is None: output = unallocated else: output = instruction if instruction == ".": continue else: print("[...] ", phex(index, 10), " | ", output) def map_pages_to_virtual(self, callee, callee_name): """ Synchronize virtual memory with the contents of memory, map memory addresses to virtual addresses. """ if self.debug_info is True: print("[V. Memory / Memory] Mapping memory pages to virtual memory...") counter = self.const_text_start all_pages = self.bus.communicate("virtual memory", callee, callee_name, "disk, all pages for mapping", "") for page in all_pages: for instruction in page.instructions: self.virtual_memory[counter] = instruction counter = counter + 4 def update_virtual_memory(self, page_number, position): """ Update the virtual memory addresses, with currently loaded memory pages. """ pass def print_virtual_with_position(self, address): """ Print the position of the simulator in virtual memory with given PC address. """ if self.debug_info is True: print("\n[V. Memory] Prining v. memory with position '" + phex(address, 5) + "':") position = int(phex(address, 8), 0) - (4*3) for pos in range(position, position+(4*7), 4): if pos == position + (4*3): print("=====>\t", "[", phex(pos, 8), "] ~ ", self.virtual_memory[pos]) else: print("\t", "[", phex(pos, 8), "] ~ ", self.virtual_memory[pos]) def find_starting_address(self): """ Find the starting position of 'main:' label in virtual memory. """ if self.debug_info is True: print("\n[V. Memory] Looking for starting address...") start_address = "" for index, instruction in enumerate(self.virtual_memory): if instruction is None: continue elif instruction[1] == "main:": start_address = phex(index, 8) break if self.debug_info is True: print("[V. Memory] Starting address found '" + start_address + "'...") return start_address def print_virtual_memory_layout(self): """ Print the contents of virtual memory. """ if self.debug_info is True: print("\n[V. Memory] Printing contents of virtual memory table...:") for index, data in enumerate(self.virtual_memory): if index == self.const_stack_start: print("[...] STACK(up): ") if index == self.const_heap_start: print("[...] HEAP(down):") if index == self.const_bss_start: print("[...] BSS:") if index == self.const_text_start: print("[...] TEXT (instructions):") if data != None: print("[...]", phex(index, 10), " ~ ", data)
class CPU: """ This class represents the CPU component. """ def __init__(self, debug): """ Initialize the data members of the component. """ self.debug_info = debug if self.debug_info is True: print("[CPU] initializing...") self.bus = Bus(debug) self.register_table = { # General purpose registers "rax": 0, "rbx": 0, "rcx": 0, "rdx": 0, "r8": 0, "r9": 0, # Special purposee registers "pc": 0, "rsp": 0, "rbp": 0, "rip": 0, "rflags": 0, } # Performance/timing variables self.current_instr_size = 0 self.current_instr_cycles = 0 self.cpi = 0 ########################### if self.debug_info is True: print("[CPU] finished initializing...") def synchronize_with_memory(self, callee, callee_name): """ Point the program counter to the first instruction to execute in virtual memory. """ starting_position = self.bus.communicate( "cpu", callee, callee_name, "virtual memory, starting position", "") self.register_table["pc"] = int(starting_position, 0) def fetch_next_instruction(self, callee, callee_name): """ Retrieve the instruction that is (size of instruction) positions from the current position pointed to by the program counter, then increment the counter that many positions. """ instruction = self.bus.communicate("cpu", callee, callee_name, "cache, give block", self.register_table["pc"] + 4) self.register_table["pc"] = self.register_table["pc"] + 4 # Cache missed if instruction == "MISS": return "CACHE MISSED" # parse instruction parsed = self.parse_instruction(instruction) return parsed def parse_instruction(self, instruction): """ Seperate given instruction string into op, dest_reg, src_reg and value variables. Then package them together into a list and return it. """ if self.debug_info is True: print("\n[CPU] parsing instruction '" + instruction + "'...") register_id = [ "rax", "rbx", "rcx", "rdx", "r8", "r9", "rsp", "rbp", "rip" ] op = "" dest_reg = "" src_reg = "" value = "" # just a label, ignore if ':' in instruction: return "label" split_instr = instruction.split(',') if len(split_instr) == 1: print(split_instr) split_instr = split_instr[0].split(' ') else: split_instr[0] = split_instr[0].split(' ', 1) split_instr = [ split_instr[0][0], split_instr[0][1], split_instr[1].lstrip(' ') ] if len(split_instr) == 2: value = split_instr[1] elif str(split_instr[2]) in register_id: src_reg = split_instr[2] dest_reg = split_instr[1] else: dest_reg = split_instr[1] value = split_instr[2] op = split_instr[0] if self.debug_info is True: print("[CPU] parsing done, result:") print("[...] length:", len(split_instr)) print("[...] split:", split_instr) print("[...] op: ", op) print("[...] dest_reg: ", dest_reg) print("[...] src_reg: ", src_reg) print("[...] value: ", value) parsed = { "op": op, "dest_reg": dest_reg, "src_reg": src_reg, "value": value } return parsed def ALU(self, op, dest_reg, src_reg, value, callee, callee_name): """ ALU will apply the given operation 'op', to the registers given or perform special behaviors. Requires virtual memory communication to point to the appropriate place when a jump is executed. """ if self.debug_info is True: print_alu_debug(op, dest_reg, src_reg, value) # Mov operation if op == "mov": # Check if moving value or register if value != "": self.register_table[dest_reg] = int(value, 0) else: self.register_table[dest_reg] = self.register_table[src_reg] # add if op == "add": # Check if adding value or register if value != "": self.register_table[dest_reg] += int(value, 0) else: self.register_table[dest_reg] += self.register_table[src_reg] # sub if op == "sub": # Check if adding value or register if value != "": self.register_table[dest_reg] -= int(value, 0) else: self.register_table[dest_reg] -= self.register_table[src_reg] # cmp if op == "cmp": self.register_table["rflags"] = ( self.register_table[dest_reg] == self.register_table[src_reg]) # jmp if op == "jmp": # self.bus.communicate("cpu", callee, callee_name, # "virtual memory, find position of label", value) self.register_table["pc"] = int(value, 0) def print_register_table(self): """ Print the value of all the registers. """ print("[CPU] Printing register table...") print("[...] RAX = ", phex(self.register_table["rax"], 32)) print("[...] RBX = ", phex(self.register_table["rbx"], 32)) print("[...] RCX = ", phex(self.register_table["rcx"], 32)) print("[...] RDX = ", phex(self.register_table["rdx"], 32)) print("[...] R8 = ", phex(self.register_table["r8"], 32)) print("[...] R9 = ", phex(self.register_table["r9"], 32)) print("[...] PC = ", phex(self.register_table["pc"], 32)) print("[...] RSP = ", phex(self.register_table["rsp"], 32)) print("[...] RBP = ", phex(self.register_table["rbp"], 32)) print("[...] RIP = ", phex(self.register_table["rip"], 32)) print("[...] RFLAGS = ", phex(self.register_table["rflags"], 6)) def print_program_with_position(self, callee, callee_name): """ Display the position of the program counter and program. """ # defualt offset 4 positions before, 4 positions after self.bus.communicate("cpu", callee, callee_name, "virtual memory, print position", self.register_table["pc"])