from argparse import ArgumentParser
from os.path import exists as f_exists
from os import remove as f_remove


# Adressmodi
IMP = 0  # Implied:     z.B. BRK, INX
ACC = 1  # Accumulator: z.B. ASL A
IMM = 2  # Immediate:   z.B. LDA #$A7
ZP  = 3  # Zeropage:    z.B. STA $60
ZPX = 4  # Zeropage X:  z.B. LDA $60,x
ABS = 5  # Absolute:    z.B. LDA $C000
ABSX = 6 # Absolute X:  z.B. LDA $6000,x
ABSY = 7 # Absolute Y:  z.B. STA $8000,y
INDY = 8 # Indirect Y:  z.B. LDA ($61),Y
IND = 9  # Indirect:    z.B. JMP ($8000)
REL = 10 # Relative:    z.B. BNE


def hexfmt4(val):
   """ 4bit-Zahl mit hex(val) umwandeln und das Ergebnis wie
       folgt anpassen:
       '0x5'  -> '5'
   """
   if val > 15:
       raise SystemExit("hexfmt4 overflow: %d" % val)
   s = hex(val)
   s = s[2:]
   return s.upper()

def hexfmt8(val):
   """ 8bit-Zahl hexadezimal ausgeben """
   return "%s%s" % (hexfmt4(val >> 4), hexfmt4(val & 0xf))


def hexfmt16(high, low):
   """ 16bit-Zahl hexadezimal ausgeben """
   return "%s%s" % (hexfmt8(high), hexfmt8(low))


def binfmt8(val):
   """ Zahl mit bin(val) umwandeln und das Ergebnis wie folgt
       anpassen:
       '0b1'     -> '0000 0001'
       '0x10000' -> '0001 0000'
   """
   if val > 255:
       raise SystemExit("binfmt8 overflow: %d" % val)
   s = bin(val)
   s = s[2:]
   x = len(s)
   while x < 8:
       s = '0' + s
       x += 1
   s = s[:4] + ' ' + s[4:]
   return s


class Opcode:
   def __init__(self, opc, txt, amode, cyc, chg_pc, func):
       self.opc = opc
       self.txt = txt
       self.amode = amode
       self.cyc = cyc
       self.changepc = chg_pc
       self.func = func
       # Befehlslaenge
       if (amode == IMP) or (amode == ACC):
           self.bytes_ = 1
       elif amode in [IMM, ZP, ZPX, INDY, REL]:
           self.bytes_ = 2
       elif amode in [ABS, ABSX, ABSY, IND]:
           self.bytes_ = 3

   def to_str(self, arg_txt=''):
       amode_txt = ""
       if self.amode == ACC:
           amode_txt = "A"
       elif self.amode == IMM:
           amode_txt = "#%s" % arg_txt
       elif self.amode in [ZP, ABS, REL]:
           amode_txt = arg_txt
       elif self.amode in [ZPX, ABSX]:
           amode_txt = "%s,X" % arg_txt
       elif self.amode == ABSY:
           amode_txt = "%s,Y" % arg_txt
       elif self.amode == INDY:
           amode_txt = "(%s),Y" % arg_txt
       elif self.amode == IND:
           amode_txt = "(%s)" % arg_txt
       return "%s %s" % (self.txt, amode_txt)


class CPU6502:
   def __init__(self):
       # Flags
       self.flag_c = False
       self.flag_n = False
       self.flag_z = False
       self.flag_i = True  # disable Interrupt
       # Stackpointer
       self.sp = 0xff
       # Programmzaehler
       self.pc = 0
       # Register
       self.reg_a = 0
       self.reg_x = 0
       self.reg_y = 0
       # 64KB Speicher
       self.mem = bytearray(65536)
       # Stackadresse
       self.stack = 0x100
       # Taktzyklen
       self.cycles = 0
       # Start und Ende des Programms
       self.start = 0
       self.end = 0
       # Symboltabelle
       # - Adresse als Schluessel, Label als Inhalt
       self.addresses = {}
       # - Label als Schluessel, Adresse als Inhalt
       self.labels = {}
       # Datei, die einen IRQ signalisiert
       self.irq_file = "/tmp/irq"
       # Datei, die einen Abbruch signalisiert
       self.brk_file = "/tmp/brk"
       # Alternative Routinen fuer JSR-Aufrufe
       # in der Form { adr: func }
       self.jsr_alt = {}

       # BEFEHLE ANLEGEN

       self.opcodes = {
       #   opc:         opc    txt   mod cyc pc     func
           0x00: Opcode(0x00, 'BRK', IMP, 7, True,  self.brk),
           0x06: Opcode(0x06, 'ASL', ZP,  5, False, self.asl_zp),
           0x08: Opcode(0x08, 'PHP', IMP, 3, False, self.php),
           0x09: Opcode(0x09, 'ORA', IMM, 2, False, self.ora_imm),
           0x0a: Opcode(0x0a, 'ASL', ACC, 2, False, self.asl),
           0x10: Opcode(0x10, 'BPL', REL, 3, True,  self.bpl),
           0x18: Opcode(0x18, 'CLC', IMP, 2, False, self.clc),
           0x20: Opcode(0x20, 'JSR', ABS, 6, True,  self.jsr_abs),
           0x26: Opcode(0x26, 'ROL', ZP,  5, False, self.rol_zp),
           0x28: Opcode(0x28, 'PLP', IMP, 4, False, self.plp),
           0x29: Opcode(0x29, 'AND', IMM, 2, False, self.and_imm),
           0x2a: Opcode(0x2a, 'ROL', ACC, 2, False, self.rol),
           0x38: Opcode(0x38, 'SEC', IMP, 2, False, self.sec),
           0x40: Opcode(0x40, 'RTI', IMP, 6, True, self.rti),
           0x46: Opcode(0x46, 'LSR', ZP,  5, False, self.lsr_zp),
           0x48: Opcode(0x48, 'PHA', IMP, 3, False, self.pha),
           0x49: Opcode(0x49, 'EOR', IMM, 2, False, self.eor_imm),
           0x4a: Opcode(0x4a, 'LSR', ACC, 2, False, self.lsr),
           0x4c: Opcode(0x4c, 'JMP', ABS, 3, True,  self.jmp_abs),
           0x58: Opcode(0x58, 'CLI', IMP, 2, False, self.cli),
           0x60: Opcode(0x60, 'RTS', IMP, 6, True,  self.rts),
           0x65: Opcode(0x65, 'ADC', ZP,  3, False, self.adc_zp),
           0x66: Opcode(0x66, 'ROR', ZP,  5, False, self.ror_zp),
           0x68: Opcode(0x68, 'PLA', IMP, 4, False, self.pla),
           0x69: Opcode(0x69, 'ADC', IMM, 2, False, self.adc_imm),
           0x6a: Opcode(0x6a, 'ROR', ACC, 2, False, self.ror),
           0x6c: Opcode(0x6c, 'JMP', IND, 3, True,  self.jmp_ind),
           0x6d: Opcode(0x6d, 'ADC', ABS, 4, False, self.adc_abs),
           0x78: Opcode(0x78, 'SEI', IMP, 2, False, self.sei),
           0x79: Opcode(0x79, 'ADC', ABSY,4, False, self.adc_abs_y),
           0x7d: Opcode(0x7d, 'ADC', ABSX,4, False, self.adc_abs_x),
           0x84: Opcode(0x84, 'STY', ZP,  3, False, self.sty_zp),
           0x85: Opcode(0x85, 'STA', ZP,  3, False, self.sta_zp),
           0x86: Opcode(0x86, 'STX', ZP,  3, False, self.stx_zp),
           0x88: Opcode(0x88, 'DEY', IMP, 2, False, self.dey),
           0x8a: Opcode(0x8a, 'TXA', IMP, 2, False, self.txa),
           0x8c: Opcode(0x8c, 'STY', ABS, 4, False, self.sty_abs),
           0x8d: Opcode(0x8d, 'STA', ABS, 4, False, self.sta_abs),
           0x8e: Opcode(0x8e, 'STX', ABS, 4, False, self.stx_abs),
           0x90: Opcode(0x90, 'BCC', REL, 3, True,  self.bcc),
           0x91: Opcode(0x91, 'STA', INDY,6, False, self.sta_ind_y),
           0x95: Opcode(0x95, 'STA', ZPX, 4, False, self.sta_zp_x),
           0x98: Opcode(0x98, 'TYA', IMP, 2, False, self.tya),
           0x99: Opcode(0x99, 'STA', ABSY,5, False, self.sta_abs_y),
           0x9d: Opcode(0x9d, 'STA', ABSX,5, False, self.sta_abs_x),
           0xa0: Opcode(0xa0, 'LDY', IMM, 2, False, self.ldy_imm),
           0xa2: Opcode(0xa2, 'LDX', IMM, 2, False, self.ldx_imm),
           0xa4: Opcode(0xa4, 'LDY', ZP,  3, False, self.ldy_zp),
           0xa5: Opcode(0xa5, 'LDA', ZP,  3, False, self.lda_zp),
           0xa6: Opcode(0xa6, 'LDX', ZP,  3, False, self.ldx_zp),
           0xa8: Opcode(0xa8, 'TAY', IMP, 2, False, self.tay),
           0xa9: Opcode(0xa9, 'LDA', IMM, 2, False, self.lda_imm),
           0xaa: Opcode(0xaa, 'TAX', IMP, 2, False, self.tax),
           0xac: Opcode(0xac, 'LDY', ABS, 4, False, self.ldy_abs),
           0xad: Opcode(0xad, 'LDA', ABS, 4, False, self.lda_abs),
           0xae: Opcode(0xae, 'LDX', ABS, 4, False, self.ldx_abs),
           0xb0: Opcode(0xb0, 'BCS', REL, 3, True,  self.bcs),
           0xb1: Opcode(0xb1, 'LDA', INDY,5, False, self.lda_ind_y),
           0xb5: Opcode(0xb5, 'LDA', ZPX, 4, False, self.lda_zp_x),
           0xb9: Opcode(0xb9, 'LDA', ABSY,4, False, self.lda_abs_y),
           0xbd: Opcode(0xbd, 'LDA', ABSX,4, False, self.lda_abs_x),
           0xc0: Opcode(0xc0, 'CPY', IMM, 2, False, self.cpy_imm),
           0xc4: Opcode(0xc4, 'CPY', ZP,  3, False, self.cpy_zp),
           0xc5: Opcode(0xc5, 'CMP', ZP,  3, False, self.cmp_zp),
           0xc6: Opcode(0xc6, 'DEC', ZP,  5, False, self.dec_zp),
           0xc8: Opcode(0xc8, 'INY', IMP, 2, False, self.iny),
           0xc9: Opcode(0xc9, 'CMP', IMM, 2, False, self.cmp_imm),
           0xca: Opcode(0xca, 'DEX', IMP, 2, False, self.dex),
           0xcc: Opcode(0xcc, 'CPY', ABS, 4, False, self.cpy_abs),
           0xcd: Opcode(0xcd, 'CMP', ABS, 4, False, self.cmp_abs),
           0xce: Opcode(0xce, 'DEC', ABS, 6, False, self.dec_abs),
           0xd0: Opcode(0xd0, 'BNE', REL, 3, True,  self.bne),
           0xd9: Opcode(0xd9, 'CMP', ABSY,4, False, self.cmp_abs_y),
           0xdd: Opcode(0xdd, 'CMP', ABSX,4, False, self.cmp_abs_x),
           0xe0: Opcode(0xe0, 'CPX', IMM, 2, False, self.cpx_imm),
           0xe4: Opcode(0xe4, 'CPX', ZP,  3, False, self.cpx_zp),
           0xe5: Opcode(0xe5, 'SBC', ZP,  3, False, self.sbc_zp),
           0xe6: Opcode(0xe6, 'INC', ZP,  5, False, self.inc_zp),
           0xe8: Opcode(0xe8, 'INX', IMP, 2, False, self.inx),
           0xe9: Opcode(0xe9, 'SBC', IMM, 2, False, self.sbc_imm),
           0xea: Opcode(0xea, 'NOP', IMP, 2, False, self.nop),
           0xec: Opcode(0xec, 'CPX', ABS, 4, False, self.cpx_abs),
           0xed: Opcode(0xed, 'SBC', ABS, 4, False, self.sbc_abs),
           0xee: Opcode(0xee, 'INC', ABS, 6, False, self.inc_abs),
           0xf0: Opcode(0xf0, 'BEQ', REL, 3, True,  self.beq),
           0xf9: Opcode(0xf9, 'SBC', ABSY,4, False, self.sbc_abs_y),
           0xfd: Opcode(0xfd, 'SBC', ABSX,4, False, self.sbc_abs_x),
       }

       # Zugriff auf die Befehle ueber Assembler anstatt Maschinencode

       self.commands = {
           'adc #':    self.opcodes[0x69],
           'adc aa':   self.opcodes[0x65],
           'adc aaaa': self.opcodes[0x6d],
           'adc aaaa,x': self.opcodes[0x7d],
           'adc aaaa,y': self.opcodes[0x79],
           'and #':    self.opcodes[0x29],
           'asl A':    self.opcodes[0x0a],
           'asl aa':   self.opcodes[0x06],
           'bcc aa':   self.opcodes[0x90],
           'bcs aa':   self.opcodes[0xb0],
           'beq aa':   self.opcodes[0xf0],
           'bne aa':   self.opcodes[0xd0],
           'bpl aa':   self.opcodes[0x10],
           'brk':      self.opcodes[0x00],
           'clc':      self.opcodes[0x18],
           'cli':      self.opcodes[0x58],
           'cmp #':    self.opcodes[0xc9],
           'cmp aa':   self.opcodes[0xc5],
           'cmp aaaa': self.opcodes[0xcd],
           'cmp aaaa,x': self.opcodes[0xdd],
           'cmp aaaa,y': self.opcodes[0xd9],
           'cpx #':    self.opcodes[0xe0],
           'cpx aa':   self.opcodes[0xe4],
           'cpx aaaa': self.opcodes[0xec],
           'cpy #':    self.opcodes[0xc0],
           'cpy aa':   self.opcodes[0xc4],
           'cpy aaaa': self.opcodes[0xcc],
           'dec aa':   self.opcodes[0xc6],
           'dec aaaa': self.opcodes[0xce],
           'dex':      self.opcodes[0xca],
           'dey':      self.opcodes[0x88],
           'eor #':    self.opcodes[0x49],
           'inc aa':   self.opcodes[0xe6],
           'inc aaaa': self.opcodes[0xee],
           'inx':      self.opcodes[0xe8],
           'iny':      self.opcodes[0xc8],
           'jmp aaaa': self.opcodes[0x4c],
           'jmp (aaaa)': self.opcodes[0x6c],
           'jsr aaaa': self.opcodes[0x20],
           'lda #':    self.opcodes[0xa9],
           'lda aa':   self.opcodes[0xa5],
           'lda aa,x': self.opcodes[0xb5],
           'lda aaaa': self.opcodes[0xad],
           'lda aaaa,x': self.opcodes[0xbd],
           'lda aaaa,y': self.opcodes[0xb9],
           'lda (aa),y': self.opcodes[0xb1],
           'ldx #':    self.opcodes[0xa2],
           'ldx aa':   self.opcodes[0xa6],
           'ldx aaaa': self.opcodes[0xae],
           'ldy #':    self.opcodes[0xa0],
           'ldy aa':   self.opcodes[0xa4],
           'ldy aaaa': self.opcodes[0xac],
           'lsr A':    self.opcodes[0x4a],
           'lsr aa':   self.opcodes[0x46],
           'nop':      self.opcodes[0xea],
           'ora #':    self.opcodes[0x09],
           'pha':      self.opcodes[0x48],
           'pla':      self.opcodes[0x68],
           'php':      self.opcodes[0x08],
           'plp':      self.opcodes[0x28],
           'rol A':    self.opcodes[0x2a],
           'rol aa':   self.opcodes[0x26],
           'ror A':    self.opcodes[0x6a],
           'ror aa':   self.opcodes[0x66],
           'rti':      self.opcodes[0x40],
           'rts':      self.opcodes[0x60],
           'sbc #':    self.opcodes[0xe9],
           'sbc aa':   self.opcodes[0xe5],
           'sbc aaaa': self.opcodes[0xed],
           'sbc aaaa,x': self.opcodes[0xfd],
           'sbc aaaa,y': self.opcodes[0xf9],
           'sei':      self.opcodes[0x78],
           'sta aa':   self.opcodes[0x85],
           'sta aa,x': self.opcodes[0x95],
           'sta aaaa': self.opcodes[0x8d],
           'sta aaaa,x': self.opcodes[0x9d],
           'sta aaaa,y': self.opcodes[0x99],
           'sta (aa),y': self.opcodes[0x91],
           'stx aa':   self.opcodes[0x86],
           'stx aaaa': self.opcodes[0x8e],
           'sty aa':   self.opcodes[0x84],
           'sty aaaa': self.opcodes[0x8c],
           'sec':      self.opcodes[0x38],
           'tax':      self.opcodes[0xaa],
           'tay':      self.opcodes[0xa8],
           'txa':      self.opcodes[0x8a],
           'tya':      self.opcodes[0x98],
       }

       # Trennlinie im Disassembler-Listing
       self.sep_lines = [0x00, 0x4c, 0x60] # BRK, JMP, RTS

       # Name und Beschreibung fuer Programmoptionen
       self.progname = 'cpu6502.py'
       self.progdesc = '6502 CPU Emulator'

   def resetcyc(self):
       self.cycles = 0

   def addcmd(self, txt, argl=0, argh=0):
       cmd = self.commands[txt]
       self.mem[self.pc] = cmd.opc
       if cmd.amode in [IMM, ZP, ZPX, INDY, ABS, ABSX, ABSY, IND, REL]:
           self.mem[self.pc+1] = argl
       if cmd.amode in [ABS, ABSX, ABSY, IND]:
           self.mem[self.pc+2] = argh
       self.pc += cmd.bytes_

   def addcmd2(self, txt, arg=0):
       cmd = self.commands[txt]
       self.mem[self.pc] = cmd.opc
       if cmd.amode in [IMM, ZP, ZPX, INDY, ABS, ABSX, ABSY, IND, REL]:
           self.mem[self.pc+1] = arg & 0xff
       if cmd.amode in [ABS, ABSX, ABSY, IND]:
           self.mem[self.pc+2] = arg >> 8
       self.pc += cmd.bytes_

   # HILFSROUTINEN ZUM SETZEN DER FLAGS

   def _a_set_nz_flags(self):
       """ Setzt Neg- und Zero-Flags fuer A """
       self.flag_z = (self.reg_a == 0)
       self.flag_n = (self.reg_a > 127)

   def _x_set_nz_flags(self):
       """ Setzt Neg- und Zero-Flags fuer X """
       self.flag_z = (self.reg_x == 0)
       self.flag_n = (self.reg_x > 127)

   def _y_set_nz_flags(self):
       """ Setzt Neg- und Zero-Flags fuer Y """
       self.flag_z = (self.reg_y == 0)
       self.flag_n = (self.reg_y > 127)

   def _mem_set_nz_flags(self, adr):
       """ Setzt Neg- und Zero-Flags anhand einer
           Speicherstelle
       """
       self.flag_z = (self.mem[adr] == 0)
       self.flag_n = (self.mem[adr] > 127)

   def _comp_set_flags(self, reg, comp):
       """ Setzt die Flags fuer die Vergleichsroutinen
           Der Parameter reg enthhaelt den Wert des Registers,
           comp den Vergleichswert (direkt bzw. aus dem Speicher).
       """
       self.flag_z = (reg == comp)
       self.flag_c = (reg >= comp)
       self.flag_n = (reg < comp)

   # HILFSROUTINE FUeR VERZWEIGUNGEN

   def _branch_rel(self):
       """ Fuehrt einen relativen Sprung aus """
       branch = self.mem[self.pc]
       # Zweier-Komplement bei Rueckwaerts-Spruengen
       # in negative Zahl umwandeln
       if branch > 127:
           branch = branch ^ 255  # xor
           branch += 1
           branch = - branch
       # Sprung gilt ab der Adresse des Folgebefehls
       # (daher noch +1)
       self.pc = self.pc + 1 + branch

   # HILFSROUTINEN FUeR ADDITION UND SUBSTRAKTION

   def _add(self, val):
       s = self.reg_a + val
       if self.flag_c:
           s += 1
       # Test auf Ueberlauf
       self.flag_c = (s != s & 255)
       self.reg_a = s & 255
       self._a_set_nz_flags()

   def _sub(self, val):
       self.reg_a = self.reg_a - val
       # Wenn das Carray-Flag vor der Substraktion nicht
       # gesetzt wurde, wird noch der Wert 1 abgezogen
       if not self.flag_c:
           self.reg_a -= 1
       # Carry-Flag setzen (bei positivem Resultat)
       self.flag_c = (self.reg_a >= 0)
       # Bei negativem Resultat Zweier-Komplement bilden
       if self.reg_a < 0:
           self.reg_a = abs(self.reg_a)
           self.reg_a = self.reg_a ^ 255 # xor
           self.reg_a += 1
       self._a_set_nz_flags()
       pass

   # HILFSROUTINEN ZUM SETZEN STACKPOINTER

   def _dec_sp(self):
       self.sp -= 1
       # Test auf Ueberlauf
       if self.sp < 0:
           self.sp = 0xff

   def _inc_sp(self):
       self.sp += 1
       # Test auf Ueberlauf
       if self.sp > 0xff:
           self.sp = 0

   def _exec_irq(self):
       # Programmzaehler auf dem Stack sichern
       self.mem[self.stack + self.sp] = self.pc >> 8
       self._dec_sp()
       self.mem[self.stack + self.sp] = self.pc & 0xff
       self._dec_sp()
       # Prozessorstatus auf dem Stack sichern
       self.php()
       # Programmzaehler setzen
       self.pc = self.mem[0xffff] << 8 | self.mem[0xfffe]
       # Disable interrupt
       self.sei()

   def _check_irq(self):
       if f_exists(self.irq_file):
           self._exec_irq()

   def _clear_irq(self):
       if f_exists(self.irq_file):
           f_remove(self.irq_file)

   # BEFEHLE

   def adc_imm(self):
       """ ADC # """
       self._add(self.mem[self.pc])

   def adc_zp(self):
       """ ADC aa """
       adr = self.mem[self.pc]
       self._add(self.mem[adr])

   def adc_abs(self):
       """ ADC aaaa """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self._add(self.mem[adr])

   def adc_abs_x(self):
       """ ADC aaaa,x """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self._add(self.mem[adr + self.reg_x])

   def adc_abs_y(self):
       """ ADC aaaa,y """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self._add(self.mem[adr + self.reg_y])

   def and_imm(self):
       """ AND # """
       self.reg_a = self.reg_a & self.mem[self.pc]
       self._a_set_nz_flags()

   def asl(self):
       """ ASL A """
       self.reg_a = self.reg_a << 1
       # Test auf Ueberlauf
       self.flag_c = (self.reg_a != self.reg_a & 255)
       self.reg_a = self.reg_a & 255
       self._a_set_nz_flags()

   def asl_zp(self):
       """ ASL aa """
       adr = self.mem[self.pc]
       res = self.mem[adr] << 1
       # Test auf Ueberlauf
       self.flag_c = (res != res & 255)
       self.mem[adr] = res & 255
       self._mem_set_nz_flags(adr)

   def bcc(self):
       """ BCC aa """
       if self.flag_c == 0:
           self._branch_rel()
       else:
           self.pc += 1

   def bcs(self):
       """ BCS aa """
       if self.flag_c == 1:
           self._branch_rel()
       else:
           self.pc += 1

   def beq(self):
       """ BEQ aa """
       if self.flag_z == 1:
           self._branch_rel()
       else:
           self.pc += 1

   def bne(self):
       """ BNE aa """
       if self.flag_z == 0:
           self._branch_rel()
       else:
           self.pc += 1

   def bpl(self):
       """ BPL aa """
       if self.flag_n == 0:
           self._branch_rel()
       else:
           self.pc += 1

   def brk(self):
       """ BRK """
       #self.pc = 0

   def clc(self):
       """ CLC """
       self.flag_c = False

   def cli(self):
       """ CLI """
       self.flag_i = False

   def cmp_imm(self):
       """ CMP # """
       self._comp_set_flags(self.reg_a, self.mem[self.pc])

   def cmp_zp(self):
       """ CMP aa """
       adr = self.mem[self.pc]
       self._comp_set_flags(self.reg_a, self.mem[adr])

   def cmp_abs(self):
       """ CMP aaaa """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self._comp_set_flags(self.reg_a, self.mem[adr])

   def cmp_abs_x(self):
       """ CMP aaaa,x """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self._comp_set_flags(self.reg_a, self.mem[adr + self.reg_x])

   def cmp_abs_y(self):
       """ CMP aaaa,y """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self._comp_set_flags(self.reg_a, self.mem[adr + self.reg_y])

   def cpx_imm(self):
       """ CPX # """
       self._comp_set_flags(self.reg_x, self.mem[self.pc])

   def cpx_zp(self):
       """ CPX aa """
       adr = self.mem[self.pc]
       self._comp_set_flags(self.reg_x, self.mem[adr])

   def cpx_abs(self):
       """ CPX aaaa """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self._comp_set_flags(self.reg_x, self.mem[adr])

   def cpy_imm(self):
       """ CPY # """
       self._comp_set_flags(self.reg_y, self.mem[self.pc])

   def cpy_zp(self):
       """ CPY aa """
       adr = self.mem[self.pc]
       self._comp_set_flags(self.reg_y, self.mem[adr])

   def cpy_abs(self):
       """ CPY aaaa """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self._comp_set_flags(self.reg_y, self.mem[adr])

   def dec_zp(self):
       """ DEC aa """
       adr = self.mem[self.pc]
       if self.mem[adr] == 0:
           self.mem[adr] = 255
       else:
           self.mem[adr] -= 1
       self._mem_set_nz_flags(adr)

   def dec_abs(self):
       """ DEC aaaa """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       if self.mem[adr] == 0:
           self.mem[adr] = 255
       else:
           self.mem[adr] -= 1
       self._mem_set_nz_flags(adr)

   def dex(self):
       """ DEX """
       self.reg_x -= 1
       if self.reg_x < 0:
           self.reg_x = 255
       self._x_set_nz_flags()

   def dey(self):
       """ DEY """
       self.reg_y -= 1
       if self.reg_y < 0:
           self.reg_y = 255
       self._y_set_nz_flags()

   def eor_imm(self):
       """ EOR # """
       self.reg_a = self.reg_a ^ self.mem[self.pc]
       self._a_set_nz_flags()

   def inc_zp(self):
       """ INC aa """
       adr = self.mem[self.pc]
       if self.mem[adr] == 255:
           self.mem[adr] = 0
       else:
           self.mem[adr] += 1
       self._mem_set_nz_flags(adr)

   def inc_abs(self):
       """ INC aaaa """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       if self.mem[adr] == 255:
           self.mem[adr] = 0
       else:
           self.mem[adr] += 1
       self._mem_set_nz_flags(adr)

   def inx(self):
       """ INX """
       self.reg_x += 1
       if self.reg_x > 255:
           self.reg_x = 0
       self._x_set_nz_flags()

   def iny(self):
       """ INY """
       self.reg_y += 1
       if self.reg_y > 255:
           self.reg_y = 0
       self._y_set_nz_flags()

   def jmp_abs(self):
       """ JMP aaaa """
       self.pc = self.mem[self.pc+1] << 8 | self.mem[self.pc]

   def jmp_ind(self):
       """ JMP (aaaa) """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self.pc = self.mem[adr+1] << 8 | self.mem[adr]

   def jsr_abs(self):
       """ JSR aaaa """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       if not adr in self.jsr_alt:
           # Programmzaehler+2 als Ruecksprungadresse auf den
           # Stack legen (PC wurde schon um eins erhoeht,
           # daher nur noch +1)
           self.mem[self.stack + self.sp] = (self.pc+1) >> 8
           self._dec_sp()
           self.mem[self.stack + self.sp] = (self.pc+1) & 0xff
           self._dec_sp()
           self.pc = adr
       else:
           # Alternativen Handler aufrufen (Programmzaehler wurde
           # schon um eins erhoeht, daher nur noch +2)
           self.jsr_alt[adr]()
           self.pc += 2

   def lda_imm(self):
       """ LDA # """
       self.reg_a = self.mem[self.pc]
       self._a_set_nz_flags()

   def lda_zp(self):
       """ LDA aa """
       adr = self.mem[self.pc]
       self.reg_a = self.mem[adr]
       self._a_set_nz_flags()

   def lda_zp_x(self):
       """ LDA aa,X """
       adr = self.mem[self.pc]
       self.reg_a = self.mem[adr + self.reg_x]
       self._a_set_nz_flags()

   def lda_abs(self):
       """ LDA aaaa """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self.reg_a = self.mem[adr]
       self._a_set_nz_flags()

   def lda_abs_x(self):
       """ LDA aaaa,X """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self.reg_a = self.mem[adr + self.reg_x]
       self._a_set_nz_flags()

   def lda_abs_y(self):
       """ LDA aaaa,Y """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self.reg_a = self.mem[adr + self.reg_y]
       self._a_set_nz_flags()

   def lda_ind_y(self):
       """ LDA (aa),Y """
       ptr = self.mem[self.pc]
       adr = (self.mem[ptr+1] << 8 | self.mem[ptr]) + self.reg_y
       self.reg_a = self.mem[adr]
       self._a_set_nz_flags()

   def ldx_imm(self):
       """ LDX # """
       self.reg_x = self.mem[self.pc]
       self._x_set_nz_flags()

   def ldx_zp(self):
       """ LDX aa """
       adr = self.mem[self.pc]
       self.reg_x = self.mem[adr]
       self._x_set_nz_flags()

   def ldx_abs(self):
       """ LDX aaaa """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self.reg_x = self.mem[adr]
       self._x_set_nz_flags()

   def ldy_imm(self):
       """ LDY # """
       self.reg_y = self.mem[self.pc]
       self._y_set_nz_flags()

   def ldy_zp(self):
       """ LDY aa """
       adr = self.mem[self.pc]
       self.reg_y = self.mem[adr]
       self._y_set_nz_flags()

   def ldy_abs(self):
       """ LDY aaaa """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self.reg_y = self.mem[adr]
       self._y_set_nz_flags()

   def lsr(self):
       """ LSR A """
       self.reg_a = self.reg_a & 255
       # Test auf vorhersehbaren Ueberlauf (Bit 0 gesetzt?)
       self.flag_c = (self.reg_a != self.reg_a & 254)
       self.reg_a = self.reg_a >> 1
       self._a_set_nz_flags()

   def lsr_zp(self):
       """ LSR aa """
       adr = self.mem[self.pc]
       val = self.mem[adr]
       # Test auf vorhersehbaren Ueberlauf (Bit 0 gesetzt?)
       self.flag_c = (val != val & 254)
       self.mem[adr] = val >> 1
       self._mem_set_nz_flags(adr)

   def nop(self):
       """ NOP """
       pass

   def ora_imm(self):
       """ ORA # """
       self.reg_a = self.reg_a | self.mem[self.pc]
       self._a_set_nz_flags()

   def pha(self):
       """ PHA """
       self.mem[self.stack + self.sp] = self.reg_a
       self._dec_sp()

   def pla(self):
       """ PLA """
       self._inc_sp()
       self.reg_a = self.mem[self.stack + self.sp]
       self._a_set_nz_flags()

   def php(self):
       """ PHP """
       preg = 0
       if self.flag_n:
           preg = preg | 0x80
       if self.flag_i:
           preg = preg | 0x04
       if self.flag_z:
           preg = preg | 0x02
       if self.flag_c:
           preg = preg | 0x01
       self.mem[self.stack + self.sp] = preg
       self._dec_sp()

   def plp(self):
       """ PLP """
       self._inc_sp()
       preg = self.mem[self.stack + self.sp]
       self.flag_n = (preg & 0x80 > 0)
       self.flag_i = (preg & 0x04 > 0)
       self.flag_z = (preg & 0x02 > 0)
       self.flag_c = (preg & 0x01 > 0)

   def rol(self):
       """ ROL A """
       self.reg_a = self.reg_a << 1
       # Carry-Flag ins Bit 0 uebertragen
       self.reg_a = self.reg_a | self.flag_c
       # Test auf Ueberlauf
       self.flag_c = (self.reg_a != self.reg_a & 255)
       self.reg_a = self.reg_a & 255
       self._a_set_nz_flags()

   def rol_zp(self):
       """ ROL aa """
       adr = self.mem[self.pc]
       res = self.mem[adr] << 1
       # Carry-Flag ins Bit 0 uebertragen
       res = res | self.flag_c
       # Test auf Ueberlauf
       self.flag_c = (res != res & 255)
       self.mem[adr] = res & 255
       self._mem_set_nz_flags(adr)

   def ror(self):
       """ ROR A """
       self.reg_a = self.reg_a & 255
       # Carry-Flag merken (fuer unten)
       cs = self.flag_c
       # Test auf vorhersehbaren Ueberlauf (Bit 0 gesetzt?)
       self.flag_c = (self.reg_a != self.reg_a & 254)
       self.reg_a = self.reg_a >> 1
       # Carry, falls es gesetzt war, ins Bit 7 uebertragen
       if cs:
           self.reg_a = self.reg_a | 128
       self._a_set_nz_flags()

   def ror_zp(self):
       """ ROR aa """
       adr = self.mem[self.pc]
       val = self.mem[adr]
       # Carry-Flag merken (fuer unten)
       cs = self.flag_c
       # Test auf vorhersehbaren Ueberlauf (Bit 0 gesetzt?)
       self.flag_c = (val != val & 254)
       self.mem[adr] = val >> 1
       # Carry, falls es gesetzt war, ins Bit 7 uebertragen
       if cs:
           self.mem[adr] = self.mem[adr] | 128
       self._mem_set_nz_flags(adr)

   def rti(self):
       """ RTI """
       self.plp()
       self._inc_sp()
       pcl = self.mem[self.stack + self.sp]
       self._inc_sp()
       pch = self.mem[self.stack + self.sp]
       self.pc = pch << 8 | pcl
       #IRQ-Quelle bereinigen
       self._clear_irq()

   def rts(self):
       """ RTS """
       self._inc_sp()
       pcl = self.mem[self.stack + self.sp]
       self._inc_sp()
       pch = self.mem[self.stack + self.sp]
       self.pc = pch << 8 | pcl
       self.pc += 1

   def sbc_imm(self):
       """ SBC # """
       self._sub(self.mem[self.pc])

   def sbc_zp(self):
       """ SBC aa """
       adr = self.mem[self.pc]
       self._sub(self.mem[adr])

   def sbc_abs(self):
       """ SBC aaaa """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self._sub(self.mem[adr])

   def sbc_abs_x(self):
       """ SBC aaaa,x """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self._sub(self.mem[adr + self.reg_x])

   def sbc_abs_y(self):
       """ SBC aaaa,y """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self._sub(self.mem[adr + self.reg_y])

   def sec(self):
       """ SEC """
       self.flag_c = True

   def sei(self):
       """ SEI """
       self.flag_i = True

   def sta_zp(self):
       """ STA zp """
       adr = self.mem[self.pc]
       self.mem[adr] = self.reg_a

   def sta_zp_x(self):
       """ STA aa,X """
       adr = self.mem[self.pc]
       self.mem[adr + self.reg_x] = self.reg_a

   def sta_abs(self):
       """ STA aaaa """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self.mem[adr] = self.reg_a

   def sta_abs_x(self):
       """ STA aaaa,X """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self.mem[adr + self.reg_x] = self.reg_a

   def sta_abs_y(self):
       """ STA aaaa,Y """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self.mem[adr + self.reg_y] = self.reg_a

   def sta_ind_y(self):
       """ STA (aa),y """
       ptr = self.mem[self.pc]
       adr = (self.mem[ptr+1] << 8 | self.mem[ptr]) + self.reg_y
       self.mem[adr] = self.reg_a

   def stx_zp(self):
       """ STX aa """
       adr = self.mem[self.pc]
       self.mem[adr] = self.reg_x

   def stx_abs(self):
       """ STX aaaa """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self.mem[adr] = self.reg_x

   def sty_zp(self):
       """ STY aa """
       adr = self.mem[self.pc]
       self.mem[adr] = self.reg_y

   def sty_abs(self):
       """ STY aaaa """
       adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
       self.mem[adr] = self.reg_y

   def tax(self):
       """ TAX """
       self.reg_x = self.reg_a
       self._x_set_nz_flags()

   def tay(self):
       """ TAY """
       self.reg_y = self.reg_a
       self._y_set_nz_flags()

   def txa(self):
       """ TXA """
       self.reg_a = self.reg_x
       self._a_set_nz_flags()

   def tya(self):
       """ TYA """
       self.reg_a = self.reg_y
       self._a_set_nz_flags()

   # HAUPTROUTINEN

   def load(self, filename):
       """ Laedt das Programm an die angegebene Adresse, die
           in den ersten beiden Bytes steht (cbm format),
           und setzt den Start und das Ende fuer das Programm
       """
       f = open(filename, 'rb')
       data = f.read()
       f.close()
       # Startadresse (die ersten zwei Bytes) laden
       slow = data[0]
       shigh = data[1]
       self.start = shigh << 8 | slow
       # restliches Programm laden
       idx = self.start
       for b in data[2:]:
           self.mem[idx] = b
           idx += 1
       self.end = idx - 1

   def loadsym(self, filename):
       f = open(filename, 'r')
       data = f.readlines()
       f.close()
       for line in data:
           parts = line.split(chr(9)) # tab sep
           # [TAB]vscroll[TAB]= $c1b0[TAB]; ?
           # -> ['', 'vscroll', '= $c1b0', '; ?']
           adr = int(parts[2][3:],16)
           label = parts[1]
           self.addresses[adr] = label
           self.labels[label] = adr

   def disass(self):
       """ Gibt das geladene Programm in Assembler aus """
       adr = self.start
       while adr <= self.end:
           if (len(self.addresses) > 0) and (adr in self.addresses):
               print("%s:" % self.addresses[adr])
           try:
               cmd = self.opcodes[self.mem[adr]]
           except KeyError:
               cmd = Opcode(None, '???', IMP, 0, False, None)
           argl = self.mem[adr+1]
           argh = self.mem[adr+2]
           print(hexfmt16(adr >> 8, adr & 0xff), end='')
           print("  ", end='')
           arg_txt = ""
           if cmd.amode in [IMM, ZP, ZPX, INDY]:
               arg_txt = hexfmt8(argl)
           elif cmd.amode in [ABS, ABSX, ABSY, IND]:
               arg_txt = "%s%s" % (hexfmt8(argh), hexfmt8(argl))
           elif cmd.amode == REL:
               # Zweier-Komplement bei Rueckwaerts-Spruengen
               # in negative Zahl umwandeln
               if argl > 127:
                   branch = argl ^ 255  # xor
                   branch += 1
                   dest = adr + 2 - branch
               else:
                   dest = adr + 2 + argl
               arg_txt = hexfmt16(dest >> 8, dest & 0xff)
           print(cmd.to_str(arg_txt), end='')
           if (len(self.addresses) > 0) and (cmd.amode in [ZP, ABS, ABSX, ABSY, IND]): # (cmd.opc in [0x20, 0x4c])
               try:
                   if cmd.amode == ZP: # spaeter: in [ZP, ZPX, ...]
                       label = self.addresses[argl]
                   else:
                       label = self.addresses[argh << 8 | argl]
                   print(" ; %s" % label, end='')
               except KeyError:
                   pass
           print("")
           if cmd.opc in self.sep_lines:
               print("------------------")
           adr += cmd.bytes_

   def run(self):
       """ Startet das geladene Programm """
       self.pc = self.start
       while True:
           opc = self.mem[self.pc]
           try:
               cmd = self.opcodes[opc]
           except KeyError:
               print("Illegal opcode: %s" % hexfmt8(opc))
               self.registers()
               raise SystemExit()
           # Programmzaehler auf das erste Argument setzen
           self.pc += 1
           cmd.func()
           # Programmzaehler weiter erhoehen, wenn es kein
           # Sprungbefehl ist
           if not cmd.changepc:
               self.pc += cmd.bytes_ - 1 # -1 wegen der Erhoehung oben
           self.cycles += self.opcodes[opc].cyc
           # Bei BRK-Befehl abbrechen
           if opc == 0:
               break
           # Bei Existenz einer Stop-Datei abbrechen
           if f_exists(self.brk_file):
               f_remove(self.brk_file)
               break
           # Interrupt
           if not self.flag_i:
               self._check_irq()
           # Debug

   def registers(self):
       """ Druckt die Register und die Flags aus """
       print("")
       print(" PC   AC  XR  YR  SP  NV-BDIZC")
       print("------------------------------")
       print("%s  %s  %s  %s  %s  %d%d---%d%d%d" % (
           hexfmt16(self.pc >> 8, self.pc & 0xff),
           hexfmt8(self.reg_a), hexfmt8(self.reg_x),
           hexfmt8(self.reg_y), hexfmt8(self.sp),
           self.flag_n, 0, self.flag_i, self.flag_z, self.flag_c
       ))

   def mempage(self, page):
       """ Druckt die angegebene Speicherseite aus """
       print("")
       print("Page %s:" % hexfmt8(page))
       print("--------")
       for i in range(16):
           print("%s: " % hexfmt8(i << 4), end='')
           for j in range(16):
               p = (i << 4) + j
               print("%s " % hexfmt8(self.mem[(page << 8) + p]), end='')
           print("")
       print("")


   def cmdtable(self):
       """ Druckt eine Tabelle aller implementierten Befehle
           sortiert nach den Opcodes aus (wie im Datenblatt von
           WDC). Aus Platzgruenden wird die Tabelle in der Mitte
           geteilt und die beiden Haelften nacheinander ausgedruckt.
       """
       # linke und rechte Haelfte
       for page in range(2):
           # Spaltentitel (unteren 4 Bits) mit 0-7 bzw. 8-F
           print("  ", end = '')
           offset = page * 8
           for i in range(8):
               print("|%s" % hexfmt4(i+offset).center(7), end='')
           print("")
           # 16 Zeilen fuer die oberen 4 Bits
           for row in range(16):
               # Trennlinie
               print("--", end = '')
               print("+-------" * 8)
               # Zeilentitel 00-F0
               print("%sx" % hexfmt4(row), end='')
               # Die Befehle werden in zwei Zeilen gedruckt
               # (1. Zeile mit Befehlsnamen und 2. Zeile mit
               # Addressierungsart). Daher werden die Texte
               # zuerst in zwei Listen gesammelt und am Ende
               # ausgedruckt
               row1 = []
               row2 = []
               for col in range(8):
                   opc = row << 4 | (col+offset)
                   try:
                       cmd = self.opcodes[opc]
                       arg_txt = ""
                       if cmd.amode in [ZP, ZPX, INDY, REL]:
                           arg_txt = "aa"
                       elif cmd.amode in [ABS, ABSX, ABSY, IND]:
                           arg_txt = "aaaa"
                       txt = cmd.to_str(arg_txt)
                       # Befehlsnamen und Adressierungsart aufteilen
                       itms = txt.split(" ")
                       row1.append(itms[0])
                       if len(itms) > 1:
                           row2.append(itms[1])
                       else:
                           row2.append("")
                   except KeyError:
                       # kein Befehl fuer diese Opcode implementiert
                       row1.append("")
                       row2.append("")
               # 1. Zeile der Befehle ausgeben
               for itm in row1:
                   print("|%s" % itm.center(7), end='')
               # 2. Zeile der Befehle ausgeben
               print("")
               print("  ", end='')
               for itm in row2:
                   print("|%s" % itm.center(7), end='')
               print("")
           # Zwischenraum
           print("")
           print("")

   def setirq(self, adr):
       """ Aktiviert den Hardware-IRQ und setzt den Zeiger
           bei $fffe
       """
       self.flag_i = False
       self.mem[0xfffe] = adr & 0xff
       self.mem[0xffff] = adr >> 8

   def addhandler(self, label, adr, handler):
       """ Setzt einen Handler fuer eine bestimmte Adresse,
           der dann von JSR aufgerufen wird.
       """
       self.jsr_alt[adr] = handler
       self.addresses[adr] = label
       self.labels[label] = adr

   def getargs(self):
       """ Verarbeitet die Parameter aus dem Programmaufruf
       """
       self.parser = ArgumentParser(
               prog=self.progname,
               description=self.progdesc
           )
       self.parser.add_argument("-r", "--reg", action='store_true',
                               help="print registers")
       self.parser.add_argument("-c", "--cyc", action='store_true',
                               help="print cycles")
       self.parser.add_argument("-m", "--mem", metavar="INT",
                               help="print memory page")
       self.parser.add_argument("-f", "--file", metavar="FILE",
                               help="program file in cbm format")
       self.parser.add_argument("-s", "--symtab", metavar="FILE",
                               help="symtable for program file")
       self.parser.add_argument("-a", "--rega", metavar="INT",
                               help="start value for A-Register")
       self.parser.add_argument("-x", "--regx", metavar="INT",
                               help="start value for X-Register")
       self.parser.add_argument("-y", "--regy", metavar="INT",
                               help="start value for Y-Register")
       self.parser.add_argument("--irq", metavar="INT",
                               help="set IRQ handler")
   #     self.parser.add_argument("--reset", metavar="INT",
   #                             help="set hardware reset handler")
   #     self.parser.add_argument("--start", metavar="ADR", default=32768,
   #                             help="start of program (default $8000)")
       self.parser.add_argument("-d", "--disass", action='store_true',
                               help="disassemble the program")
       self.parser.add_argument("--run", action='store_true',
                               help="run the program")
       self.parser.add_argument("--jsr", action='store_true',
                               help="create jsr call for program")
       self.parser.add_argument("--commands", action='store_true',
                               help="print command table")

   def parseargs(self):
       """ Ruft den Argument-Parser auf (in einer extra Methode,
           damit abgeleitete Klassen noch Argumente hinzufuegen
           koennen, da ein mehrmaliger Aufruf von parse_args nicht
           funktioniert hat)
       """
       self.args = self.parser.parse_args()

   def executeargs(self):
       """ Fuehrt die beim Programm uebergebenen Argumente aus
       """
       if self.args.file:
           self.load(self.args.file)
       if self.args.symtab:
           self.loadsym(self.args.symtab)
       if self.args.commands:
           self.cmdtable()
       if self.args.disass:
           self.disass()
       if self.args.rega:
           self.reg_a = int(self.args.rega)
       if self.args.regx:
           self.reg_x = int(self.args.regx)
       if self.args.regy:
           self.reg_y = int(self.args.regy)
       if self.args.irq:
           try:
               adr = int(self.args.irq)      # Dezimalwert
           except ValueError:
               adr = int(self.args.irq, 16)  # Hex-Wert
           self.setirq(adr)
       if self.args.jsr:
           start_prg = self.start
           self.start = 0x0000
           self.addcmd2('jsr aaaa', start_prg)
           self.addcmd('brk')
       if self.args.run:
           self.run()
       if self.args.reg:
           self.registers()
       if self.args.cyc:
           print("Cycles: %d" % self.cycles)
       if self.args.mem:
           self.mempage(int(self.args.mem))


if __name__ == "__main__":

   cpu = CPU6502()
   cpu.getargs()
   cpu.parseargs()
   cpu.executeargs()