#! /usr/bin/env python from __future__ import print_function import sys import time import random from optparse import OptionParser # to make Python2 and Python3 act the same -- how dumb def random_seed(seed): try: random.seed(seed, version=1) except: random.seed(seed) return def time_clock(): try: rc = time_clock() except: rc = time.process_time() return rc # # HELPER # def dospace(howmuch): for i in range(howmuch): print('%24s' % ' ', end=' ') # useful instead of assert def zassert(cond, str): if cond == False: print('ABORT::', str) exit(1) return class cpu: # # INIT: how much memory? # def __init__(self, memory, memtrace, regtrace, cctrace, compute, verbose, printstats, headercount): # # CONSTANTS # # conditions self.COND_GT = 0 self.COND_GTE = 1 self.COND_LT = 2 self.COND_LTE = 3 self.COND_EQ = 4 self.COND_NEQ = 5 # registers in system self.REG_ZERO = 0 self.REG_AX = 1 self.REG_BX = 2 self.REG_CX = 3 self.REG_DX = 4 self.REG_EX = 5 self.REG_FX = 6 self.REG_SP = 7 self.REG_BP = 8 # system memory: in KB self.max_memory = memory * 1024 # which memory addrs and registers to trace? self.memtrace = memtrace self.regtrace = regtrace self.cctrace = cctrace self.compute = compute self.verbose = verbose self.printstats = printstats self.headercount = headercount self.PC = 0 self.registers = {} self.conditions = {} self.labels = {} self.vars = {} self.memory = {} self.pmemory = {} # for printable version of what's in memory (instructions) self.condlist = [self.COND_GTE, self.COND_GT, self.COND_LTE, self.COND_LT, self.COND_NEQ, self.COND_EQ] self.regnums = [self.REG_ZERO, self.REG_AX, self.REG_BX, self.REG_CX, self.REG_DX, self.REG_EX, self.REG_FX, self.REG_SP, self.REG_BP] self.regnames = {} self.regnames['zero'] = self.REG_ZERO # hidden zero-valued register self.regnames['ax'] = self.REG_AX self.regnames['bx'] = self.REG_BX self.regnames['cx'] = self.REG_CX self.regnames['dx'] = self.REG_DX self.regnames['ex'] = self.REG_EX self.regnames['fx'] = self.REG_FX self.regnames['sp'] = self.REG_SP self.regnames['bp'] = self.REG_BP tmplist = [] for r in self.regtrace: zassert(r in self.regnames, 'Register %s cannot be traced because it does not exist' % r) tmplist.append(self.regnames[r]) self.regtrace = tmplist self.init_memory() self.init_registers() self.init_condition_codes() # # BEFORE MACHINE RUNS # def init_condition_codes(self): for c in self.condlist: self.conditions[c] = False return def init_memory(self): for i in range(self.max_memory): self.memory[i] = 0 return def init_registers(self): for i in self.regnums: self.registers[i] = 0 return def dump_memory(self): print('MEMORY DUMP') for i in range(self.max_memory): if i not in self.pmemory and i in self.memory and self.memory[i] != 0: print(' m[%d]' % i, self.memory[i]) return # # INFORMING ABOUT THE HARDWARE # def get_regnum(self, name): assert(name in self.regnames) return self.regnames[name] def get_regname(self, num): assert(num in self.regnums) for rname in self.regnames: if self.regnames[rname] == num: return rname return '' def get_regnums(self): return self.regnums def get_condlist(self): return self.condlist def get_reg(self, reg): assert(reg in self.regnums) return self.registers[reg] def get_cond(self, cond): assert(cond in self.condlist) return self.conditions[cond] def get_pc(self): return self.PC def set_reg(self, reg, value): assert(reg in self.regnums) self.registers[reg] = value def set_cond(self, cond, value): assert(cond in self.condlist) self.conditions[cond] = value def set_pc(self, pc): self.PC = pc # # INSTRUCTIONS # def halt(self): return -1 def iyield(self): return -2 def nop(self): return 0 def rdump(self): print('REGISTERS::', end=' ') print('ax:', self.registers[self.REG_AX], end=' ') print('bx:', self.registers[self.REG_BX], end=' ') print('cx:', self.registers[self.REG_CX], end=' ') print('dx:', self.registers[self.REG_DX], end=' ') print('ex:', self.registers[self.REG_EX], end=' ') print('fx:', self.registers[self.REG_FX], end=' ') return def mdump(self, index): print('m[%d] ' % index, self.memory[index]) return # # MEMORY MOVES # def move_i_to_r(self, src, dst): self.registers[dst] = src return 0 # memory: value, register, register def move_i_to_m(self, src, value, reg1, reg2, scale): tmp = value + self.registers[reg1] + (scale * self.registers[reg2]) self.memory[tmp] = src return 0 def move_m_to_r(self, value, reg1, reg2, scale, dst): tmp = value + self.registers[reg1] + (scale * self.registers[reg2]) self.registers[dst] = self.memory[tmp] def move_r_to_m(self, src, value, reg1, reg2, scale): tmp = value + self.registers[reg1] + (scale * self.registers[reg2]) self.memory[tmp] = self.registers[src] return 0 def move_r_to_r(self, src, dst): self.registers[dst] = self.registers[src] return 0 # # LOAD EFFECTIVE ADDRESS (everything but the final change of memory value) # def lea_m_to_r(self, value, reg1, reg2, scale, dst): tmp = value + self.registers[reg1] + (scale * self.registers[reg2]) self.registers[dst] = tmp # # ARITHMETIC INSTRUCTIONS # def add_i_to_r(self, src, dst): self.registers[dst] += src return 0 def add_r_to_r(self, src, dst): self.registers[dst] += self.registers[src] return 0 def mul_i_to_r(self, src, dst): self.registers[dst] *= src return 0 def mul_r_to_r(self, src, dst): self.registers[dst] *= self.registers[src] return 0 def sub_i_to_r(self, src, dst): self.registers[dst] -= src return 0 def sub_r_to_r(self, src, dst): self.registers[dst] -= self.registers[src] return 0 def neg_r(self, src): self.registers[src] = -self.registers[src] # # SUPPORT FOR LOCKS # def atomic_exchange(self, src, value, reg1, reg2): tmp = value + self.registers[reg1] + self.registers[reg2] old = self.memory[tmp] self.memory[tmp] = self.registers[src] self.registers[src] = old return 0 def fetchadd(self, src, value, reg1, reg2): tmp = value + self.registers[reg1] + self.registers[reg2] old = self.memory[tmp] self.memory[tmp] = self.memory[tmp] + self.registers[src] self.registers[src] = old # # TEST for conditions # def test_all(self, src, dst): self.init_condition_codes() if dst > src: self.conditions[self.COND_GT] = True if dst >= src: self.conditions[self.COND_GTE] = True if dst < src: self.conditions[self.COND_LT] = True if dst <= src: self.conditions[self.COND_LTE] = True if dst == src: self.conditions[self.COND_EQ] = True if dst != src: self.conditions[self.COND_NEQ] = True return 0 def test_i_r(self, src, dst): self.init_condition_codes() return self.test_all(src, self.registers[dst]) def test_r_i(self, src, dst): self.init_condition_codes() return self.test_all(self.registers[src], dst) def test_r_r(self, src, dst): self.init_condition_codes() return self.test_all(self.registers[src], self.registers[dst]) # # JUMPS # def jump(self, targ): self.PC = targ return 0 def jump_notequal(self, targ): if self.conditions[self.COND_NEQ] == True: self.PC = targ return 0 def jump_equal(self, targ): if self.conditions[self.COND_EQ] == True: self.PC = targ return 0 def jump_lessthan(self, targ): if self.conditions[self.COND_LT] == True: self.PC = targ return 0 def jump_lessthanorequal(self, targ): if self.conditions[self.COND_LTE] == True: self.PC = targ return 0 def jump_greaterthan(self, targ): if self.conditions[self.COND_GT] == True: self.PC = targ return 0 def jump_greaterthanorequal(self, targ): if self.conditions[self.COND_GTE] == True: self.PC = targ return 0 # # CALL and RETURN # def call(self, targ): self.registers[self.REG_SP] -= 4 self.memory[self.registers[self.REG_SP]] = self.PC self.PC = targ def ret(self): self.PC = self.memory[self.registers[self.REG_SP]] self.registers[self.REG_SP] += 4 # # STACK and related # def push_r(self, reg): self.registers[self.REG_SP] -= 4 self.memory[self.registers[self.REG_SP]] = self.registers[reg] return 0 def push_m(self, value, reg1, reg2, scale): self.registers[self.REG_SP] -= 4 tmp = value + self.registers[reg1] + (self.registers[reg2] * scale) # push address onto stack, not memory value itself self.memory[self.registers[self.REG_SP]] = tmp return 0 def pop(self): self.registers[self.REG_SP] += 4 def pop_r(self, dst): self.registers[dst] = self.memory[self.registers[self.REG_SP]] self.registers[self.REG_SP] += 4 # # HELPER func for getarg # def register_translate(self, r): if r in self.regnames: return self.regnames[r] zassert(False, 'Register %s is not a valid register' % r) return def getregname(self, r): t = r.strip() if t == '': return 'zero' zassert(t[0] == '%', 'Expecting a proper register name, got [%s]' % r) return r.split('%')[1].strip() # # HELPER in parsing mov (quite primitive) and other ops # returns: (value, type) # where type is (TYPE_REGISTER, TYPE_IMMEDIATE, TYPE_MEMORY) # # FORMATS # %ax - register # $10 - immediate # 10 - direct memory # 10(%ax) - memory + reg indirect # 10(%ax,%bx) - memory + 2 reg indirect # 10(%ax,%bx,4) - XXX (not handled) # def getarg(self, arg): tmp1 = arg.replace(',', ' ') tmp = tmp1.replace(' \t', '') if tmp[0] == '$': # this is an IMMEDIATE VALUE value = tmp.split('$')[1] neg = 1 if value[0] == '-': value = value[1:] neg = -1 zassert(value.isdigit(), 'value [%s] must be a digit' % value) return neg * int(value), 'TYPE_IMMEDIATE' elif tmp[0] == '%': # this is a REGISTER register = tmp.split('%')[1] return self.register_translate(register), 'TYPE_REGISTER' elif tmp[0] == '.': # this is a LABEL targ = tmp return targ, 'TYPE_LABEL' elif tmp[0].isalpha() and not tmp[0].isdigit(): # this is a VARIABLE zassert(tmp in self.vars, 'Variable %s is not declared' % tmp) return '%d,%d,%d,1' % (self.vars[tmp], self.register_translate('zero'), self.register_translate('zero')), 'TYPE_MEMORY' elif tmp[0].isdigit() or tmp[0] == '-' or tmp[0] == '(': # MOST GENERAL CASE: number(reg,reg) or number(reg) or number(reg,reg,number) neg = 1 if tmp[0] == '-': tmp = tmp[1:] neg = -1 s = tmp.split('(') if len(s) == 1: # no parens -> we just assume that we have a constant value (an address), e.g., mov 10, %ax value = neg * int(tmp) return '%d,%d,%d,1' % (int(value), self.register_translate('zero'), self.register_translate('zero')), 'TYPE_MEMORY' elif len(s) == 2: # here we just assume that we have something in parentheses # e.g., mov 10(%ax) or mov 10(%ax,%bx) or mov 10(%ax,%bx,10) or mov (%ax,%bx,10) or ... # if no leading number exists, first char should be a paren; in that case, value is just made to be 0 # otherwise we should handle either a number or a negative number if tmp[0] != '(': zassert(s[0].strip().isdigit() == True, 'First number should be a digit [%s]' % s[0]) value = neg * int(s[0]) else: value = 0 t = s[1].split(')')[0].split('__BREAK__') if len(t) == 1: register = self.getregname(t[0]) return '%d,%d,%d,1' % (int(value), self.register_translate(register), self.register_translate('zero')), 'TYPE_MEMORY' elif len(t) == 2: register1 = self.getregname(t[0]) register2 = self.getregname(t[1]) return '%d,%d,%d,1' % (int(value), self.register_translate(register1), self.register_translate(register2)), 'TYPE_MEMORY' elif len(t) == 3: register1 = self.getregname(t[0]) register2 = self.getregname(t[1]) scale = int(t[2]) return '%d,%d,%d,%d' % (int(value), self.register_translate(register1), self.register_translate(register2), scale), 'TYPE_MEMORY' else: print('mov: bad argument [%s]' % tmp) exit(1) return else: print('mov: bad argument [%s]' % tmp) exit(1) return zassert(True, 'mov: bad argument [%s]' % arg) return # # helper function in parsing complex args to mov/lea instruction # def removecommas(self, cline, inargs): inparen = False outargs = '' for i in range(len(inargs)): if inargs[i] == '(': zassert(inparen == False, 'cannot have nested parenthesis in argument [%s]' % cline) inparen = True if inargs[i] == ')': zassert(inparen == True, 'cannot have right parenthesis without first having left one [%s]' % cline) inparen = False if inparen == True: if inargs[i] == ',': outargs += '__BREAK__' else: outargs += inargs[i] else: outargs += inargs[i] zassert(inparen == False, 'did not close parentheses [%s]' % cline) return outargs # # LOAD a program into memory # make it ready to execute # def load(self, infile, loadaddr): pc = int(loadaddr) fd = open(infile) bpc = loadaddr data = 100 for line in fd: cline = line.rstrip() # remove everything after the comment marker ctmp = cline.split('#') assert(len(ctmp) == 1 or len(ctmp) == 2) if len(ctmp) == 2: cline = ctmp[0] # remove empty lines, and split line by spaces tmp = cline.split() if len(tmp) == 0: continue # only pay attention to labels and variables if tmp[0] == '.var': assert(len(tmp) == 2 or len(tmp) == 3) assert(tmp[0] not in self.vars) self.vars[tmp[1]] = data mul = 1 if len(tmp) == 3: mul = int(tmp[2]) data += (4 * mul) zassert(data < bpc, 'Load address overrun by static data') if self.verbose: print('ASSIGN VAR', tmp[0], "-->", tmp[1], self.vars[tmp[1]]) elif tmp[0][0] == '.': assert(len(tmp) == 1) self.labels[tmp[0]] = int(pc) if self.verbose: print('ASSIGN LABEL', tmp[0], "-->", pc) else: pc += 1 fd.close() if self.verbose: print('') # second pass: do everything else pc = int(loadaddr) fd = open(infile) for line in fd: cline = line.rstrip() # remove everything after the comment marker ctmp = cline.split('#') assert(len(ctmp) == 1 or len(ctmp) == 2) if len(ctmp) == 2: cline = ctmp[0] # remove empty lines, and split line by spaces tmp = cline.split() if len(tmp) == 0: continue # skip labels: all else must be instructions if cline[0] != '.': tmp = cline.split(None, 1) opcode = tmp[0] self.pmemory[pc] = cline.strip() if self.verbose == True: print('opcode', opcode) # MAIN OPCODE LOOP if opcode == 'mov': # most painful one to parse (due to generic form) # could be mov x(r1,r2,4), r3 or mov r1, (r2,r3) or ... outargs = self.removecommas(cline, tmp[1]) rtmp = outargs.split(',') zassert(len(rtmp) == 2, 'mov: needs two args, separated by commas [%s]' % cline) arg1 = rtmp[0].strip() arg2 = rtmp[1].strip() (src, stype) = self.getarg(arg1) (dst, dtype) = self.getarg(arg2) # print 'MOV', src, stype, dst, dtype if stype == 'TYPE_MEMORY' and dtype == 'TYPE_MEMORY': print('bad mov: two memory arguments') exit(1) elif stype == 'TYPE_IMMEDIATE' and dtype == 'TYPE_IMMEDIATE': print('bad mov: two immediate arguments') exit(1) elif stype == 'TYPE_IMMEDIATE' and dtype == 'TYPE_REGISTER': self.memory[pc] = 'self.move_i_to_r(%d, %d)' % (int(src), dst) elif stype == 'TYPE_IMMEDIATE' and dtype == 'TYPE_REGISTER': self.memory[pc] = 'self.move_i_to_r(%d, %d)' % (int(src), dst) elif stype == 'TYPE_MEMORY' and dtype == 'TYPE_REGISTER': tmp = src.split(',') assert(len(tmp) == 4) self.memory[pc] = 'self.move_m_to_r(%d, %d, %d, %d, %d)' % (int(tmp[0]), int(tmp[1]), int(tmp[2]), int(tmp[3]), dst) elif stype == 'TYPE_REGISTER' and dtype == 'TYPE_MEMORY': tmp = dst.split(',') assert(len(tmp) == 4) self.memory[pc] = 'self.move_r_to_m(%d, %d, %d, %d, %d)' % (src, int(tmp[0]), int(tmp[1]), int(tmp[2]), int(tmp[3])) elif stype == 'TYPE_REGISTER' and dtype == 'TYPE_REGISTER': self.memory[pc] = 'self.move_r_to_r(%d, %d)' % (src, dst) elif stype == 'TYPE_IMMEDIATE' and dtype == 'TYPE_MEMORY': tmp = dst.split(',') assert(len(tmp) == 4) self.memory[pc] = 'self.move_i_to_m(%d, %d, %d, %d, %d)' % (src, int(tmp[0]), int(tmp[1]), int(tmp[2]), int(tmp[3])) else: zassert(False, 'malformed mov instruction') elif opcode == 'lea': rtmp = tmp[1].split(',', 1) zassert(len(tmp) == 2 and len(rtmp) == 2, 'lea: needs two args, separated by commas [%s]' % cline) arg1 = rtmp[0].strip() arg2 = rtmp[1].strip() (src, stype) = self.getarg(arg1) (dst, dtype) = self.getarg(arg2) if stype == 'TYPE_MEMORY' and dtype == 'TYPE_REGISTER': tmp = src.split(',') assert(len(tmp) == 4) self.memory[pc] = 'self.lea_m_to_r(%d, %d, %d, %d, %d)' % (int(tmp[0]), int(tmp[1]), int(tmp[2]), int(tmp[3]), dst) else: zassert(False, 'malformed lea instruction (should be memory address source to register destination') elif opcode == 'neg': zassert(len(tmp) == 2, 'neg: takes one argument') arg = tmp[1].strip() (dst, dtype) = self.getarg(arg) zassert(dtype == 'TYPE_REGISTER', 'Can only neg a register') self.memory[pc] = 'self.neg_r(%d)' % dst elif opcode == 'pop': if len(tmp) == 1: self.memory[pc] = 'self.pop()' elif len(tmp) == 2: arg = tmp[1].strip() (dst, dtype) = self.getarg(arg) zassert(dtype == 'TYPE_REGISTER', 'Can only pop into a register') self.memory[pc] = 'self.pop_r(%d)' % dst else: zassert(False, 'pop instruction must take zero/one args') elif opcode == 'push': (src, stype) = self.getarg(tmp[1].strip()) if stype == 'TYPE_REGISTER': self.memory[pc] = 'self.push_r(%d)' % (int(src)) elif stype == 'TYPE_MEMORY': tmp = src.split(',') assert(len(tmp) == 4) self.memory[pc] = 'self.push_m(%d,%d,%d,%d)' % (int(tmp[0]), int(tmp[1]), int(tmp[2]), int(tmp[3])) else: zassert(False, 'Cannot push anything but registers') elif opcode == 'call': (targ, ttype) = self.getarg(tmp[1].strip()) if ttype == 'TYPE_LABEL': self.memory[pc] = 'self.call(%d)' % (int(self.labels[targ])) else: zassert(False, 'Cannot call anything but a label') elif opcode == 'ret': assert(len(tmp) == 1) self.memory[pc] = 'self.ret()' elif opcode == 'mul': rtmp = tmp[1].split(',', 1) zassert(len(tmp) == 2 and len(rtmp) == 2, 'mul: needs two args, separated by commas [%s]' % cline) arg1 = rtmp[0].strip() arg2 = rtmp[1].strip() (src, stype) = self.getarg(arg1) (dst, dtype) = self.getarg(arg2) if stype == 'TYPE_IMMEDIATE' and dtype == 'TYPE_REGISTER': self.memory[pc] = 'self.mul_i_to_r(%d, %d)' % (int(src), dst) elif stype == 'TYPE_REGISTER' and dtype == 'TYPE_REGISTER': self.memory[pc] = 'self.mul_r_to_r(%d, %d)' % (int(src), dst) else: zassert(False, 'malformed usage of add instruction') elif opcode == 'add': rtmp = tmp[1].split(',', 1) zassert(len(tmp) == 2 and len(rtmp) == 2, 'add: needs two args, separated by commas [%s]' % cline) arg1 = rtmp[0].strip() arg2 = rtmp[1].strip() (src, stype) = self.getarg(arg1) (dst, dtype) = self.getarg(arg2) if stype == 'TYPE_IMMEDIATE' and dtype == 'TYPE_REGISTER': self.memory[pc] = 'self.add_i_to_r(%d, %d)' % (int(src), dst) elif stype == 'TYPE_REGISTER' and dtype == 'TYPE_REGISTER': self.memory[pc] = 'self.add_r_to_r(%d, %d)' % (int(src), dst) else: zassert(False, 'malformed usage of add instruction') elif opcode == 'sub': rtmp = tmp[1].split(',', 1) zassert(len(tmp) == 2 and len(rtmp) == 2, 'sub: needs two args, separated by commas [%s]' % cline) arg1 = rtmp[0].strip() arg2 = rtmp[1].strip() (src, stype) = self.getarg(arg1) (dst, dtype) = self.getarg(arg2) if stype == 'TYPE_IMMEDIATE' and dtype == 'TYPE_REGISTER': self.memory[pc] = 'self.sub_i_to_r(%d, %d)' % (int(src), dst) elif stype == 'TYPE_REGISTER' and dtype == 'TYPE_REGISTER': self.memory[pc] = 'self.sub_r_to_r(%d, %d)' % (int(src), dst) else: zassert(False, 'malformed usage of sub instruction') elif opcode == 'fetchadd': rtmp = tmp[1].split(',', 1) zassert(len(tmp) == 2 and len(rtmp) == 2, 'fetchadd: needs two args, separated by commas [%s]' % cline) arg1 = rtmp[0].strip() arg2 = rtmp[1].strip() (src, stype) = self.getarg(arg1) (dst, dtype) = self.getarg(arg2) tmp = dst.split(',') assert(len(tmp) == 4) if stype == 'TYPE_REGISTER' and dtype == 'TYPE_MEMORY': self.memory[pc] = 'self.fetchadd(%d, %d, %d, %d)' % (src, int(tmp[0]), int(tmp[1]), int(tmp[2])) else: zassert(False, 'poorly specified fetch and add') elif opcode == 'xchg': rtmp = tmp[1].split(',', 1) zassert(len(tmp) == 2 and len(rtmp) == 2, 'xchg: needs two args, separated by commas [%s]' % cline) arg1 = rtmp[0].strip() arg2 = rtmp[1].strip() (src, stype) = self.getarg(arg1) (dst, dtype) = self.getarg(arg2) tmp = dst.split(',') assert(len(tmp) == 4) if stype == 'TYPE_REGISTER' and dtype == 'TYPE_MEMORY': self.memory[pc] = 'self.atomic_exchange(%d, %d, %d, %d)' % (src, int(tmp[0]), int(tmp[1]), int(tmp[2])) else: zassert(False, 'poorly specified atomic exchange') elif opcode == 'test': rtmp = tmp[1].split(',', 1) zassert(len(tmp) == 2 and len(rtmp) == 2, 'test: needs two args, separated by commas [%s]' % cline) arg1 = rtmp[0].strip() arg2 = rtmp[1].strip() (src, stype) = self.getarg(arg1) (dst, dtype) = self.getarg(arg2) if stype == 'TYPE_IMMEDIATE' and dtype == 'TYPE_REGISTER': self.memory[pc] = 'self.test_i_r(%d, %d)' % (int(src), dst) elif stype == 'TYPE_REGISTER' and dtype == 'TYPE_REGISTER': self.memory[pc] = 'self.test_r_r(%d, %d)' % (int(src), dst) elif stype == 'TYPE_REGISTER' and dtype == 'TYPE_IMMEDIATE': self.memory[pc] = 'self.test_r_i(%d, %d)' % (int(src), dst) else: zassert(False, 'malformed usage of test instruction') elif opcode == 'j': (targ, ttype) = self.getarg(tmp[1].strip()) zassert(ttype == 'TYPE_LABEL', 'bad jump target [%s]' % tmp[1].strip()) self.memory[pc] = 'self.jump(%d)' % int(self.labels[targ]) elif opcode == 'jne': (targ, ttype) = self.getarg(tmp[1].strip()) zassert(ttype == 'TYPE_LABEL', 'bad jump target [%s]' % tmp[1].strip()) self.memory[pc] = 'self.jump_notequal(%d)' % int(self.labels[targ]) elif opcode == 'je': (targ, ttype) = self.getarg(tmp[1].strip()) zassert(ttype == 'TYPE_LABEL', 'bad jump target [%s]' % tmp[1].strip()) self.memory[pc] = 'self.jump_equal(%d)' % self.labels[targ] elif opcode == 'jlt': (targ, ttype) = self.getarg(tmp[1].strip()) zassert(ttype == 'TYPE_LABEL', 'bad jump target [%s]' % tmp[1].strip()) self.memory[pc] = 'self.jump_lessthan(%d)' % int(self.labels[targ]) elif opcode == 'jlte': (targ, ttype) = self.getarg(tmp[1].strip()) zassert(ttype == 'TYPE_LABEL', 'bad jump target [%s]' % tmp[1].strip()) self.memory[pc] = 'self.jump_lessthanorequal(%s)' % self.labels[targ] elif opcode == 'jgt': (targ, ttype) = self.getarg(tmp[1].strip()) zassert(ttype == 'TYPE_LABEL', 'bad jump target [%s]' % tmp[1].strip()) self.memory[pc] = 'self.jump_greaterthan(%d)' % int(self.labels[targ]) elif opcode == 'jgte': (targ, ttype) = self.getarg(tmp[1].strip()) zassert(ttype == 'TYPE_LABEL', 'bad jump target [%s]' % tmp[1].strip()) self.memory[pc] = 'self.jump_greaterthanorequal(%s)' % self.labels[targ] elif opcode == 'nop': self.memory[pc] = 'self.nop()' elif opcode == 'halt': self.memory[pc] = 'self.halt()' elif opcode == 'yield': self.memory[pc] = 'self.iyield()' elif opcode == 'rdump': self.memory[pc] = 'self.rdump()' elif opcode == 'mdump': self.memory[pc] = 'self.mdump(%s)' % tmp[1] else: print('illegal opcode: ', opcode) exit(1) if self.verbose: print('pc:%d LOADING %20s --> %s' % (pc, self.pmemory[pc], self.memory[pc])) # INCREMENT PC for loader pc += 1 # END: loop over file fd.close() if self.verbose: print('') return # END: load def print_headers(self, procs): # print some headers if self.printstats == True: print('icount', end=' ') if len(self.memtrace) > 0: for m in self.memtrace: if m[0].isdigit(): print('%5d' % int(m), end=' ') else: zassert(m in self.vars, 'Traced variable %s not declared' % m) print('%5s' % m, end=' ') print(' ', end=' ') if len(self.regtrace) > 0: for r in self.regtrace: print('%5s' % self.get_regname(r), end=' ') print(' ', end=' ') if cctrace == True: print('>= > <= < != ==', end=' ') # and per thread for i in range(procs.getnum()): print(' Thread %d ' % i, end=' ') print('') return def print_trace(self, newline): if self.printstats == True: print('%6d' % self.icount, end=' ') if len(self.memtrace) > 0: for m in self.memtrace: if self.compute: if m[0].isdigit(): print('%5d' % self.memory[int(m)], end=' ') else: zassert(m in self.vars, 'Traced variable %s not declared' % m) print('%5d' % self.memory[self.vars[m]], end=' ') else: print('%5s' % '?', end=' ') print(' ', end=' ') if len(self.regtrace) > 0: for r in self.regtrace: if self.compute: print('%5d' % self.registers[r], end=' ') else: print('%5s' % '?', end=' ') print(' ', end=' ') if cctrace == True: for c in self.condlist: if self.compute: if self.conditions[c]: print('1 ', end=' ') else: print('0 ', end=' ') else: print('? ', end=' ') if (len(self.memtrace) > 0 or len(self.regtrace) > 0 or cctrace == True) and newline == True: print('') return def setint(self, intfreq, intrand): if intrand == False: return intfreq return int(random.random() * intfreq) + 1 def run(self, procs, intfreq, intrand): # hw init: cc's, interrupt frequency, etc. if procs.ismanual() == True: intfreq = 1 interrupt = 1 intrand = False interrupt = self.setint(intfreq, intrand) self.icount = 0 # you always get one printing print('') self.print_headers(procs) print('') self.print_trace(True) while True: if self.headercount > 0 and self.icount % self.headercount == 0 and self.icount > 0: print('') self.print_headers(procs) print('') self.print_trace(True) # need thread ID of current process tid = procs.getcurr().gettid() # FETCH prevPC = self.PC instruction = self.memory[self.PC] self.PC += 1 # DECODE and EXECUTE # key: self.PC may be changed during eval; thus MUST be incremented BEFORE eval rc = eval(instruction) # tracing details: ALWAYS AFTER EXECUTION OF INSTRUCTION self.print_trace(False) # output: thread-proportional spacing followed by PC and instruction dospace(tid) print(prevPC, self.pmemory[prevPC]) self.icount += 1 # halt instruction issued if rc == -1: procs.done() # finish execution by returning from run() if procs.numdone() == procs.getnum(): return self.icount procs.next() procs.restore() self.print_trace(False) for i in range(procs.getnum()): print('----- Halt;Switch ----- ', end=' ') print('') # do interrupt processing # just counts down the interrupt counter to zero # when it gets to 0, or when the 'yield' instruction is issued (rc=-2) # a switch takes place # key thing: if manual scheduling is done (procsched), interrupt # must take place every instruction for this to work interrupt -= 1 if interrupt == 0 or rc == -2: interrupt = self.setint(intfreq, intrand) curr = procs.getcurr() procs.save() procs.next() procs.restore() next = procs.getcurr() if procs.ismanual() == False or (procs.ismanual() == True and curr != next): self.print_trace(False) for i in range(procs.getnum()): print('------ Interrupt ------ ', end=' ') print('') # END: while return # # END: class cpu # # # PROCESS LIST class # # Tracks all running processes in the program # Also deals with manual scheduling as specified by user # class proclist: def __init__(self): self.plist = [] # list of process objects self.active = 0 # tracks how many processes are active self.manual = False self.procsched = [] # list of which processes to run in what order (by ID) self.curr = 0 # currently running process (index into procsched list) def finalize(self, procsched): if procsched == '': for i in range(len(self.plist)): self.procsched.append(i) self.curr = 0 self.restore() return # in this case, user has passed in schedule self.manual = True for i in range(len(procsched)): p = int(procsched[i]) if p >= self.getnum(): print('bad schedule: cannot include a thread that does not exist (%d)' % p) exit(1) self.procsched.append(p) check = [] for p in self.procsched: if p not in check: check.append(p) if len(check) != self.active: print('bad schedule: does not include ALL processes', self.procsched) exit(1) self.curr = 0 self.restore() return def addproc(self, p): self.active += 1 self.plist.append(p) return def ismanual(self): return self.manual def done(self): p = self.procsched[self.curr] self.plist[p].setdone() self.active -= 1 return def numdone(self): return len(self.plist) - self.active def getnum(self): return len(self.plist) def getcurr(self): return self.plist[self.procsched[self.curr]] def save(self): self.plist[self.procsched[self.curr]].save() return def restore(self): self.plist[self.procsched[self.curr]].restore() return def next(self): while True: self.curr += 1 if self.curr == len(self.procsched): self.curr = 0 p = self.procsched[self.curr] if self.plist[p].isdone() == False: return return # # PROCESS class # class process: def __init__(self, cpu, tid, pc, stackbottom, reginit): self.cpu = cpu # object reference self.tid = tid self.pc = pc self.regs = {} self.cc = {} self.done = False self.stack = stackbottom # init regs: all 0 or specially set to something for r in self.cpu.get_regnums(): self.regs[r] = 0 if reginit != '': # form: ax=1,bx=2 (for some subset of registers) for r in reginit.split(':'): tmp = r.split('=') assert(len(tmp) == 2) self.regs[self.cpu.get_regnum(tmp[0])] = int(tmp[1]) # init CCs for c in self.cpu.get_condlist(): self.cc[c] = False # stack self.regs[self.cpu.get_regnum('sp')] = stackbottom return def gettid(self): return self.tid def save(self): self.pc = self.cpu.get_pc() for c in self.cpu.get_condlist(): self.cc[c] = self.cpu.get_cond(c) for r in self.cpu.get_regnums(): self.regs[r] = self.cpu.get_reg(r) def restore(self): self.cpu.set_pc(self.pc) for c in self.cpu.get_condlist(): self.cpu.set_cond(c, self.cc[c]) for r in self.cpu.get_regnums(): self.cpu.set_reg(r, self.regs[r]) def setdone(self): self.done = True def isdone(self): return self.done == True # # main program # parser = OptionParser() parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed') parser.add_option('-t', '--threads', default=2, help='number of threads', action='store', type='int', dest='numthreads') parser.add_option('-p', '--program', default='', help='source program (in .s)', action='store', type='string', dest='progfile') parser.add_option('-i', '--interrupt', default=50, help='interrupt frequency', action='store', type='int', dest='intfreq') parser.add_option('-P', '--procsched', default='', help='control exactly which thread runs when', action='store', type='string', dest='procsched') parser.add_option('-r', '--randints', default=False, help='if interrupts are random', action='store_true', dest='intrand') parser.add_option('-a', '--argv', default='', help='comma-separated per-thread args (e.g., ax=1,ax=2 sets thread 0 ax reg to 1 and thread 1 ax reg to 2); specify multiple regs per thread via colon-separated list (e.g., ax=1:bx=2,cx=3 sets thread 0 ax and bx and just cx for thread 1)', action='store', type='string', dest='argv') parser.add_option('-L', '--loadaddr', default=1000, help='address where to load code', action='store', type='int', dest='loadaddr') parser.add_option('-m', '--memsize', default=128, help='size of address space (KB)', action='store', type='int', dest='memsize') parser.add_option('-M', '--memtrace', default='', help='comma-separated list of addrs to trace (e.g., 20000,20001)', action='store', type='string', dest='memtrace') parser.add_option('-R', '--regtrace', default='', help='comma-separated list of regs to trace (e.g., ax,bx,cx,dx)', action='store', type='string', dest='regtrace') parser.add_option('-C', '--cctrace', default=False, help='should we trace condition codes', action='store_true', dest='cctrace') parser.add_option('-S', '--printstats',default=False, help='print some extra stats', action='store_true', dest='printstats') parser.add_option('-v', '--verbose', default=False, help='print some extra info', action='store_true', dest='verbose') parser.add_option('-H', '--headercount',default=-1, help='how often to print a row header', action='store', type='int', dest='headercount') parser.add_option('-c', '--compute', default=False, help='compute answers for me', action='store_true', dest='solve') (options, args) = parser.parse_args() print('ARG seed', options.seed) print('ARG numthreads', options.numthreads) print('ARG program', options.progfile) print('ARG interrupt frequency', options.intfreq) print('ARG interrupt randomness',options.intrand) print('ARG procsched', options.procsched) print('ARG argv', options.argv) print('ARG load address', options.loadaddr) print('ARG memsize', options.memsize) print('ARG memtrace', options.memtrace) print('ARG regtrace', options.regtrace) print('ARG cctrace', options.cctrace) print('ARG printstats', options.printstats) print('ARG verbose', options.verbose) print('') seed = int(options.seed) random_seed(seed) numthreads = int(options.numthreads) intfreq = int(options.intfreq) zassert(intfreq > 0, 'Interrupt frequency must be greater than 0') intrand = int(options.intrand) progfile = options.progfile zassert(progfile != '', 'Program file must be specified') argv = options.argv.split(',') zassert(len(argv) == numthreads or len(argv) == 1, 'argv: must be one per-thread or just one set of values for all threads') procsched = options.procsched loadaddr = options.loadaddr memsize = options.memsize memtrace = [] if options.memtrace != '': for m in options.memtrace.split(','): memtrace.append(m) regtrace = [] if options.regtrace != '': for r in options.regtrace.split(','): regtrace.append(r) cctrace = options.cctrace printstats = options.printstats verbose = options.verbose hdrcount = options.headercount # # MAIN program # debug = False debug = False cpu = cpu(memsize, memtrace, regtrace, cctrace, options.solve, verbose, printstats, hdrcount) # load a program cpu.load(progfile, loadaddr) # process list procs = proclist() pid = 0 stack = memsize * 1000 for t in range(numthreads): if len(argv) > 1: arg = argv[pid] else: arg = argv[0] procs.addproc(process(cpu, pid, loadaddr, stack, arg)) stack -= 1000 pid += 1 # get first process ready to run procs.finalize(procsched) # run it if printstats: t1 = time_clock() ic = cpu.run(procs, intfreq, intrand) if printstats: t2 = time_clock() if printstats: print('') print('STATS:: Instructions %d' % ic) print('STATS:: Emulation Rate %.2f kinst/sec' % (float(ic) / float(t2 - t1) / 1000.0)) # use this for profiling # import cProfile # cProfile.run('run()')