# pipe.py - Pipeline simulator import re def clear(stage): contents[stage] = [ "", 0, -1, -1, -1] # Move an instruction from stage #a to stage #b. def move(a, b): for i in range(0, 5): contents[b][i] = contents[a][i] clear(a) # Read the next line of standard input. # If no more input, Python will report EOFError. # If successful, return an instruction, i.e. a list of its 5 elements. # In Python, "None" means an object has no value specified. # We are given ths instruction number, and the line of code to parse. def fetch(inst_number, line): tok = re.compile("[ $,()]") tokenlist = tok.split(line) tokenlist = [s for s in tokenlist if len(s) > 0] name = tokenlist[0] # The register operands are simply stored as int values. $17 = 17 if name in ["add", "sub", "mul", "div", "rem", "slt", "sll", "srl", \ "sra", "rol", "ror", "and", "or", "xor"]: dest = int(tokenlist[1]) src1 = int(tokenlist[2]) src2 = int(tokenlist[3]) elif name in ["lw", "lb"]: dest = int(tokenlist[1]) src1 = int(tokenlist[3]) # throw away offset in tokenlist[2] src2 = NO_REG elif name in ["sw", "sb"]: dest = NO_REG src1 = int(tokenlist[1]) src2 = int(tokenlist[3]) # throw away offset in tokenlist[2] return [name, inst_number, dest, src1, src2] # See if the pipeline is empty yet. Check all 5 pipeline stages. def is_empty(): for i in range(0, 5): if contents[i][NUMBER] != NO_INST: return False return True # ------------------------------------------------------------------- # Main program IF = 0 ID = 1 EX = 2 MEM = 3 WB = 4 NAME = 0 NUMBER = 1 DEST = 2 SRC1 = 3 SRC2 = 4 NO_INST = 0 NO_REG = -1 inst_number = 0 cycle = 0 # An instruction has 5 pieces of information, indexed 0-4 as follows: # (0) It's name, (1) its number within the program, (2) dest reg, # (3) src1 reg, (4) src2 reg contents = [["", 0, -1, -1, -1], \ ["", 0, -1, -1, -1], \ ["", 0, -1, -1, -1], \ ["", 0, -1, -1, -1], \ ["", 0, -1, -1, -1]] reg_available = [ 0 ] * 32 # Store the text of the program as a list of strings. # One string per line of assembly code. code = [] filename = input("What is the name of the input file? ") infile = open(filename, "r") for line in infile: code.append(line) infile.close() print("cycle: IF ID EX MEM WB\n") print("----------------------------------------------\n") # This big while loop basically attempts to move the contents of each # pipeline stage over one. Each iteration corresponds to one cycle. ex_available = 0 while True: # Keep track of new cycle number. The variable cycle is used for # determining stalls and for output later. cycle += 1 # Empty the WB stage. clear(WB) # Move the MEM instruction (if there is one) into the WB stage. if contents[MEM][NUMBER] != NO_INST: move(MEM, WB) # If the instruction in the EX stage is not done executing, then # we can't move it out yet. This is a structural hazard. # We should skip over the big else case and go down to the part # that tests to see if the pipeline is empty. # Otherwise, when the EX is finished, move the instruciton into # the MEM stage. # *********************************************************************** # *** You need to add code around here that detects the structural hazard. # *********************************************************************** move(EX, MEM) # Next, we try to move the instruction in ID over to EX. if contents[ID][NUMBER] != NO_INST: # We can't move out of the ID stage unless our source # operand registers are available. # ******************************************************************* # *** You need to add code here that checks to see if both of our # *** source operands are available. If not, we can't yet move out # *** of the iD stage. # ******************************************************************* # This is the case where we can go from the ID stage into # the EX stage. At this point, we need to keep track of how # long the EX stage will take. For most instructions, this # will be 1 cycle. move(ID, EX); # ********************************************************************* # *** You need to add code here that will help you later determine # *** when this instruction is finished using the EX stage. # *** Assume that a "mul" instruction will be done 13 cycles from now, # *** a "div" or "rem" will be done 36 cycles from now, and all other # *** types of instructions will be done 1 cycle from now. # ********************************************************************* # Prepare for data hazard, but not if dest is $0. # ********************************************************************** # *** You need to add code here that will help you later determine # *** when the destination operand of this instruction will be loaded # *** from memory (pertains to load instructions only). # *** Assume that the register will be available 2 cycles from now. # ********************************************************************* # If the ID stage is now empty and there is an instruction in the # IF stage, then move it over into ID. Be careful with == and != here. if contents[ID][NUMBER] == NO_INST and contents[IF][NUMBER] != NO_INST: move(IF, ID) # Now, fetch the next instruction, as long as the previous # instruction actually moved into the ID stage. # Beware of running out of instructions. next_inst = None if contents[IF][NUMBER] == NO_INST and \ inst_number < len(code): inst_number += 1 next_inst = fetch(inst_number, code[inst_number - 1]) # If we didn't just get to the end of input (no instruction # fetched), copy information about this new instruction. if next_inst == None: clear(IF) else: contents[IF] = [next_inst[NAME], next_inst[NUMBER], \ next_inst[DEST], next_inst[SRC1], next_inst[SRC2]] # If the pipeline is empty, then we are done. if is_empty(): break # Print the contents of the pipeline during this cycle. print("{0:5d}".format(cycle), end = "") for i in range(0, 5): if contents[i][NUMBER] != NO_INST: print("{0:8d}".format(contents[i][NUMBER]), end = "") else: print(" ", end = "") print()