#! /usr/bin/env python

import sys
import time
import random
from optparse import OptionParser

#
# HELPER
#
def dospace(howmuch):
    for i in range(howmuch):
        print '%24s' % ' ',

# 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

    def init_memory(self):
        for i in range(self.max_memory):
            self.memory[i] = 0

    def init_registers(self):
        for i in self.regnums:
            self.registers[i] = 0

    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]

    #
    # 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::',
        print 'ax:', self.registers[self.REG_AX], 
        print 'bx:', self.registers[self.REG_BX], 
        print 'cx:', self.registers[self.REG_CX], 
        print 'dx:', self.registers[self.REG_DX],
        print 'ex:', self.registers[self.REG_EX],
        print 'fx:', self.registers[self.REG_FX],
        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',
        if len(self.memtrace) > 0:
            for m in self.memtrace:
                if m[0].isdigit():
                    print '%5d' % int(m),
                else:
                    zassert(m in self.vars, 'Traced variable %s not declared' % m)
                    print '%5s' % m,
            print ' ',
        if len(self.regtrace) > 0:
            for r in self.regtrace:
                print '%5s' % self.get_regname(r),
            print ' ',
        if cctrace == True:
            print '>= >  <= <  != ==', 

        # and per thread
        for i in range(procs.getnum()):
            print '       Thread %d        ' % i,
        print ''
        return

    def print_trace(self, newline):
        if self.printstats == True:
            print '%6d' % self.icount, 
        if len(self.memtrace) > 0:
            for m in self.memtrace:
                if self.compute:
                    if m[0].isdigit():
                        print '%5d' % self.memory[int(m)],
                    else:
                        zassert(m in self.vars, 'Traced variable %s not declared' % m)
                        print '%5d' % self.memory[self.vars[m]],
                else:
                    print '%5s' % '?',
            print ' ',
        if len(self.regtrace) > 0:
            for r in self.regtrace:
                if self.compute:
                    print '%5d' % self.registers[r],
                else:
                    print '%5s' % '?',
            print ' ',
        if cctrace == True:
            for c in self.condlist:
                if self.compute:
                    if self.conditions[c]:
                        print '1 ',
                    else:
                        print '0 ',
                else:
                    print '? ',
        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 ----- ',
                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 ------ ',
                    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)
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
t1 = time.clock()
ic = cpu.run(procs, intfreq, intrand)
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()')