class CPU:
    """Main CPU class."""

    def __init__(self):
        """Construct a new CPU."""
        self.reg = [0b0] * 8
        self.ram = RAM()
        self.pc = 0
        self.mar = None  # holds address currently being read or written
        self.mdr = None  # holds value to write or value just read
        self.dispatch_table = {}
        self.setup_instructions()

    def setup_instructions(self):
        self.dispatch_table[0b10000010] = ldi
        self.dispatch_table[0b01000111] = prn
        self.dispatch_table[0b00000001] = halt
        self.dispatch_table[0b10100010] = mult

    def load(self, program=[]):
        """Load a program into memory."""

        address = 0

        program = [int(line, 2) for line in program]

        for instruction in program:
            self.ram.write(address, instruction)
            address += 1

    def ram_read(self, address):
        """Takes an address and returns the corresponding value in MAR"""
        return self.ram.read(address)

    def ram_write(self, address, value):
        self.ram.write(address, value)
        return self

    def alu(self, op, reg_a, reg_b):
        """ALU operations."""

        if op == "ADD":
            self.reg[reg_a] += self.reg[reg_b]
        # elif op == "SUB": etc
        else:
            raise Exception("Unsupported ALU operation")

    def trace(self):
        """
        Handy function to print out the CPU state. You might want to call this
        from run() if you need help debugging.
        """

        print(f"TRACE: %02X | %02X %02X %02X |" % (
            self.pc,
            # self.fl,
            # self.ie,
            self.ram_read(self.pc),
            self.ram_read(self.pc + 1),
            self.ram_read(self.pc + 2)
        ), end='')

        for i in range(8):
            print(" %02X" % self.reg[i], end='')

        print()

    def run(self):
        """Run the CPU."""
        while self.pc < len(self.ram):
            self.mar = self.ram_read(self.pc)

            if self.mar in self.dispatch_table:
                self.mdr = self.dispatch_table[self.mar]
                pc_delta = self.mdr(self.ram, self.pc, self.reg)
                self.pc += pc_delta
                continue

            self.pc += 1
示例#2
0
文件: cpu.py 项目: AnicJov/py6502
class CPU(Thread):
    def __init__(self,
                 mode=0,
                 frequency=1,
                 rom_path="ROM/test13.bin",
                 ram=None,
                 console=True):
        """

            -- Threading --

        """
        Thread.__init__(self)
        """
        
            -- RAM --
            
        """
        if ram is None:
            self.ram = RAM()
        else:
            self.ram = ram
        """
        
            -- Registers --
            
        """

        # - Main registers -
        self.AX = 0b00000000  # Accumulator

        # - Index registers -
        self.X = 0b00000000  # X index
        self.Y = 0b00000000  # Y index
        self.SP = 0b0000000111111111  # Stack Pointer

        # - Program counter -
        self.PC = 0b0000000000000000  # Program Counter

        # - Status register -
        #
        # Legend:
        # N - Negative, V - Overflow, B - Breakpoint
        # D - BCD, I - Interrupt, Z - Zero, C - Carry
        #
        #              NV-BDIZC
        #              || |||||
        self.flags = 0b00100000  # Processor flags
        """
        
            -- Other --
            
        """

        self.name = "MOS Technology 6502"  # Full name of the processor
        self.frequency = frequency  # Working frequency in Hz
        self.running = False  # If true, execute instructions
        self.mode = mode  # 0 for Asynchronous, 1 for Step
        self.rom_path = rom_path  # Path to a ROM
        self.offset = 0  # Memory offset of the program
        self.rom = None  # ROM binary information
        self.console = console  # If true prints CPU and RAM info every tick
        self.lookup_table = []  # Maps bytes to instructions

    def __repr__(self):
        sp = bfmt(self.SP, 16)
        pc = bfmt(self.PC, 16)
        flg = bfmt(self.flags)
        run = "True " if self.running else "False"
        mode = "Async" if self.mode == 0 else "Step "

        state = "+=================" + "===========================+\n"\
                "|          CPU: " + self.name + "          |\n"\
                "+=================+" + "==========================+\n"\
                "| AX |  " + bfmt(self.AX) + "  | SP |  " + sp[:8] + " " + sp[8:] + "  |\n"\
                "+-----------------+" + "--------------------------+\n"\
                "|  X |  " + bfmt(self.X) + "  | PC |  " + pc[:8] + " " + pc[8:] + "  |\n"\
                "+-----------------+" + "--------------------------+\n"\
                "|  Y |  " + bfmt(self.Y) + "  | Flags |        " + flg + "  |\n"\
                "+=================+" + "================||||||||==+\n"\
                "| Running: " + run + "  | Mode: " + mode + " |  NV-BDIZC  |\n"\
                "+=================+" + "==========================+\n"

        # | Uncomment for a memory dump in case of debugging
        # v
        self.ram.dump_heap()

        return state + "\n" + str(self.ram)

    def run(self):
        """ Starts the clock and starts executing instructions """

        # Sets up instruction lookup table
        self.setup_lookup_table()

        # Sets the rom field to file contents
        self.load_rom(self.rom_path)

        self.running = True

        # For snake.bin, sets the lastKey variable to key_D
        self.ram.write(0xff, 0x64)

        # Starting address of program
        self.PC = 0x0600

        # Runs tick at a certain frequency if mode is 0,
        # or as fast as possible if mode is something else
        if self.mode == 0:
            while self.running:
                freq(self.tick, self.frequency)
        else:
            while self.running:
                self.tick()

    def tick(self):
        """ Fetches instruction, executes it and progresses the program counter """

        # Generate random number in memory location 0xFE for use in programs
        self.ram.write(0xfe, randint(0, 255))

        # Offset the program counter
        self.PC += self.offset
        index = self.PC
        self.offset = 0

        # Print UI into console
        if self.console:
            clear()
            print(self)

        # Run instructions
        self.decode_instruction(self.ram.heap[index:index + 3])

        # Progress the program counter
        self.PC += 1

        # In case of interrupt
        if check_bit(self.flags, 4):
            if self.mode != 0:
                input()
            self.running = False
            if self.console:
                clear()
                print(self)
            input("<Breakpoint>")
            self.running = True
            if self.console:
                clear()
                print(self)
        else:
            if self.mode != 0:
                input()

    def load_rom(self, path):
        """ Loads a file specified in <path> into memory starting from address 0x0600 """

        with open(path, 'rb') as rom:
            i = 0

            for byte in rom.read():
                self.ram.write(0x0600 + i, byte)
                i += 1

    def setup_lookup_table(self):
        self.lookup_table = [self.unk] * 0x100

        self.lookup_table[0xEA] = lambda: self.nop()

        self.lookup_table[0xA9] = lambda: self.lda(self.immediate())
        self.lookup_table[0xA5] = lambda: self.lda(self.zero_page())
        self.lookup_table[0xB5] = lambda: self.lda(self.zero_page_x())
        self.lookup_table[0xA1] = lambda: self.lda(self.indirect_x())
        self.lookup_table[0xB1] = lambda: self.lda(self.indirect_y())

        self.lookup_table[0xA2] = lambda: self.ldx(self.immediate())
        self.lookup_table[0xA6] = lambda: self.ldx(self.zero_page())

        self.lookup_table[0xA0] = lambda: self.ldy(self.immediate())

        self.lookup_table[0x4A] = lambda: self.lsr(self.accumulator())
        self.lookup_table[0x46] = lambda: self.lsr(self.zero_page())
        self.lookup_table[0x4E] = lambda: self.lsr(self.absolute())

        self.lookup_table[0xE9] = lambda: self.sbc(self.immediate())
        self.lookup_table[0xE5] = lambda: self.sbc(self.zero_page())
        self.lookup_table[0xED] = lambda: self.sbc(self.absolute())

        self.lookup_table[0x38] = lambda: self.sec()

        self.lookup_table[0x85] = lambda: self.sta(self.zero_page())
        self.lookup_table[0x95] = lambda: self.sta(self.zero_page_x())
        self.lookup_table[0x8D] = lambda: self.sta(self.absolute())
        self.lookup_table[0x99] = lambda: self.sta(self.absolute_y())
        self.lookup_table[0x81] = lambda: self.sta(self.indirect_x())
        self.lookup_table[0x91] = lambda: self.sta(self.indirect_y())

        self.lookup_table[0x8E] = lambda: self.stx(self.absolute())
        self.lookup_table[0x96] = lambda: self.stx(self.zero_page_y())

        self.lookup_table[0x8C] = lambda: self.sty(self.absolute())

        self.lookup_table[0xAA] = lambda: self.tax()
        self.lookup_table[0x8A] = lambda: self.txa()

        self.lookup_table[0xE6] = lambda: self.inc(self.zero_page())
        self.lookup_table[0xE8] = lambda: self.inx()
        self.lookup_table[0xC8] = lambda: self.iny()

        self.lookup_table[0xC6] = lambda: self.dec(self.zero_page())
        self.lookup_table[0xCE] = lambda: self.dec(self.absolute())
        self.lookup_table[0xCA] = lambda: self.dex()

        self.lookup_table[0x29] = lambda: self.land(self.immediate())

        self.lookup_table[0x69] = lambda: self.adc(self.immediate())
        self.lookup_table[0x65] = lambda: self.adc(self.zero_page())

        self.lookup_table[0x24] = lambda: self.bit(self.zero_page())
        self.lookup_table[0x2C] = lambda: self.bit(self.absolute())

        self.lookup_table[0x00] = lambda: self.brk()

        self.lookup_table[0x90] = lambda: self.bcc(self.relative())
        self.lookup_table[0xB0] = lambda: self.bcs(self.relative())
        self.lookup_table[0xF0] = lambda: self.beq(self.relative())
        self.lookup_table[0xD0] = lambda: self.bne(self.relative())
        self.lookup_table[0x10] = lambda: self.bpl(self.relative())

        self.lookup_table[0x18] = lambda: self.clc()

        self.lookup_table[0xC9] = lambda: self.cmp(self.immediate())
        self.lookup_table[0xC5] = lambda: self.cmp(self.zero_page())
        self.lookup_table[0xE0] = lambda: self.cpx(self.immediate())
        self.lookup_table[0xE4] = lambda: self.cpx(self.zero_page())
        self.lookup_table[0xC0] = lambda: self.cpy(self.immediate())

        self.lookup_table[0x4C] = lambda: self.jmp(self.absolute())
        self.lookup_table[0x6C] = lambda: self.jmp(self.indirect())
        self.lookup_table[0x20] = lambda: self.jsr(self.absolute())

        self.lookup_table[0x60] = lambda: self.rts()

        self.lookup_table[0x48] = lambda: self.pha()
        self.lookup_table[0x68] = lambda: self.pla()

    def decode_instruction(self, instruction):
        """ Looks up instruction in memory and calls the appropriate function """

        try:
            opcode = instruction[0]

            if self.console:
                print("Current instruction: $", end='')
                print(hfmt(opcode))

            self.lookup_table[opcode]()

        except IndexError:
            # Stop running and set the break flag
            self.running = False
            self.flags = set_bit(self.flags, 4)

            # Refresh UI
            clear()
            print(self)

            print("End of ROM")
            input("Press <Enter> to exit...")
            exit()

    """
    
        -- Processor state checks --
        
    """

    def zero_check(self, val):
        """ Sets zero flag appropriately for the value <val> passed """

        if val == 0:
            self.flags = set_bit(self.flags, 1, 1)
        else:
            self.flags = set_bit(self.flags, 1, 0)

    def negative_check(self, val):
        """ Sets negative flag appropriately for the value <val> passed """

        if check_bit(val, 7):
            self.flags = set_bit(self.flags, 7, 1)
        else:
            self.flags = set_bit(self.flags, 7, 0)

    def carry_check(self, val):
        """ Sets carry flag appropriately for the value <val> passed """

        if val > 255:
            self.flags = set_bit(self.flags, 0, 1)
        else:
            self.flags = set_bit(self.flags, 0, 0)

    def overflow_check(self, val1, val2):
        """ Sets overflow flag appropriately for the values <val1> and <val2> passed """

        val_sum = val1 + val2
        if check_bit(val1, 7) and check_bit(val2,
                                            7) and (not check_bit(val_sum, 7)):
            self.flags = set_bit(self.flags, 6)
        elif (not check_bit(val1, 7)) and (not check_bit(
                val2, 7)) and check_bit(val_sum, 7):
            self.flags = set_bit(self.flags, 6)
        else:
            self.flags = set_bit(self.flags, 6, 0)

    """
    
        -- Addressing Modes --
    
    """

    def implied(self):
        self.offset += 0

        return None

    def implicit(self):
        self.offset += 0

        return None

    def accumulator(self):
        self.offset += 0

        return None

    def immediate(self):
        self.offset += 1

        return self.PC + 1

    def zero_page(self):
        self.offset += 1

        return self.ram.read(self.PC + 1)

    def zero_page_x(self):
        rel_addr = self.ram.read(self.PC + 1)

        if rel_addr + self.X > 255:
            addr = rel_addr + self.X - 255
        else:
            addr = rel_addr + self.X

        self.offset += 1

        return addr

    def zero_page_y(self):
        rel_addr = self.ram.read(self.PC + 1)

        if rel_addr + self.Y > 255:
            addr = rel_addr + self.Y - 255
        else:
            addr = rel_addr + self.Y

        self.offset += 1

        return addr

    def relative(self):
        self.offset += 1

        return self.PC + 1

    def absolute(self):
        addr = hcat(self.ram.read(self.PC + 2), self.ram.read(self.PC + 1))

        self.offset += 2

        return addr

    def absolute_x(self):
        addr = hcat(self.ram.read(self.PC + 2),
                    self.ram.read(self.PC + 1)) + self.X

        self.offset += 2

        return addr

    def absolute_y(self):
        addr = hcat(self.ram.read(self.PC + 2),
                    self.ram.read(self.PC + 1)) + self.Y

        self.offset += 2

        return addr

    def indirect(self):
        addr = hcat(self.ram.read(self.PC + 2), self.ram.read(self.PC + 1))

        self.offset += 2

        return hcat(self.ram.read(addr), self.ram.read(addr + 1))

    def indirect_x(self):
        ind_addr = hcat(self.ram.read(self.PC + 2), self.ram.read(self.PC + 1))

        if ind_addr + self.X > 255:
            rel_addr = ind_addr + self.X - 255
        else:
            rel_addr = ind_addr + self.X

        addr = hcat(self.ram.read(rel_addr), self.ram.read(rel_addr + 1))

        self.offset += 1

        return addr

    def indirect_y(self):
        addr = hcat(self.ram.read(self.ram.read(self.PC + 2) + 1),
                    self.ram.read(self.ram.read(self.PC + 1))) + self.Y

        self.offset += 1

        return addr

    """
    
        -- Instructions --
        
    """
    """ - UNK - Unknown Instruction """

    def unk(self, opcode=0xFF):
        print("UNK $" + hfmt(opcode))

        self.offset += 0

    """ - NOP - No Operation """

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

        self.offset += 0

    """ - LDA - Load Accumulator - """

    def lda(self, addr):
        if is_immediate(addr):
            print("LDA #$" + hfmt(self.ram.read(addr)))
        else:
            print("LDA $" + hfmt(addr))

        self.AX = self.ram.read(addr)

        # Set zero flag
        self.zero_check(self.AX)
        # Set negative flag
        self.negative_check(self.AX)

    """ - LDX - Load X Register """

    def ldx(self, addr):
        if is_immediate(addr):
            print("LDX #$" + hfmt(self.ram.read(addr)))
        else:
            print("LDX $" + hfmt(addr))

        self.X = self.ram.read(addr)
        # Set zero flag
        self.zero_check(self.X)
        # Set negative flag
        self.negative_check(self.X)

    """ - LDY - Load Y Register """

    def ldy(self, addr):
        if is_immediate(addr):
            print("LDY #$" + hfmt(self.ram.read(addr)))
        else:
            print("LDY $" + hfmt(addr))

        self.Y = self.ram.read(addr)
        # Set zero flag
        self.zero_check(self.Y)
        # Set negative flag
        self.negative_check(self.Y)

    """ - LSR - Logical Shift Right """

    def lsr(self, addr):
        # Accumulator
        if addr is None:
            print("LSR")

            self.flags = set_bit(self.flags, 0, check_bit(self.AX, 0))
            self.AX = self.AX >> 1

        # All other addressing modes
        else:
            print("LSR $" + hfmt(addr))

            val = self.ram.read(addr)

            self.flags = set_bit(self.flags, 0, check_bit(val, 0))
            self.ram.write(addr, val >> 1)

    """ - SBC - Subtract with Carry """

    def sbc(self, addr):
        if is_immediate(addr):
            print("SBC #$" + hfmt(self.ram.read(addr)))
        else:
            print("SBC $" + hfmt(addr))

        val = self.ram.read(addr)

        result, carry = bsub(self.AX, val)
        result, carry = bsub(result, check_bit(self.flags, 0))

        # Set carry flag
        if carry:
            self.flags = set_bit(self.flags, 0, 1)
        else:
            self.flags = set_bit(self.flags, 0, 0)
        # Set zero flag
        self.zero_check(result)
        # Set overflow flag
        self.overflow_check(self.AX, val)
        # Set negative flag
        self.negative_check(result)

        self.AX = result

    """ - SEC - Set Carry Flag """

    def sec(self):
        print("SEC")

        self.flags = set_bit(self.flags, 0, 1)

    """ - STA - Store Accumulator """

    def sta(self, addr):
        print("STA $" + hfmt(addr))

        self.ram.write(addr, self.AX)

    """ - STX - Store X Register """

    def stx(self, addr):
        print("STX $" + hfmt(addr))

        self.ram.write(addr, self.X)

    """ - STY - Store Y Register """

    def sty(self, addr):
        print("STY $" + hfmt(addr))

        self.ram.write(addr, self.Y)

    """ - TAX - Transfer Accumulator to X """

    def tax(self):
        print("TAX")

        self.X = self.AX
        # Set zero flag
        self.zero_check(self.X)
        # Set negative flag
        self.negative_check(self.X)

    """ - TXA - Transfer X to Accumulator """

    def txa(self):
        print("TXA")

        self.AX = self.X

        # Set zero flag
        self.zero_check(self.AX)
        # Set negative flag
        self.negative_check(self.AX)

    """ - INC - Increment Memory """

    def inc(self, addr):
        val = self.ram.read(addr)

        self.ram.write(addr, badd(val, 1)[0])

        # Set zero flag
        self.zero_check(val)
        # Set negative flag
        self.negative_check(val)

    """ - INX - Increment X Register """

    def inx(self):
        print("INX")

        self.X = badd(self.X, 1)[0]

        # Set zero flag
        self.zero_check(self.X)
        # Set negative flag
        self.negative_check(self.X)

    """ - INY - Increment Y Register """

    def iny(self):
        print("INY")

        self.Y = badd(self.Y, 1)[0]

        # Set zero flag
        self.zero_check(self.Y)
        # Set negative flag
        self.negative_check(self.Y)

    """ - DEC - Decrement Memory """

    def dec(self, addr):
        print("DEC $" + hfmt(addr))

        val = self.ram.read(addr) - 1

        if val < 0:
            self.ram.write(addr, 255)
        else:
            self.ram.write(addr, val)

    """ - DEX - Decrement X Register """

    def dex(self):
        print("DEX")

        if self.X - 1 < 0:
            self.X = 255
        else:
            self.X -= 1

        # Set zero flag
        self.zero_check(self.X)
        # Set negative flag
        self.negative_check(self.X)

    """ - AND - Logical AND """

    def land(self, addr):
        if is_immediate(addr):
            print("AND #$" + hfmt(self.ram.read(addr)))
        else:
            print("AND $" + hfmt(addr))

        val = self.ram.read(addr)

        self.AX = self.AX & val

        self.zero_check(self.AX)
        self.negative_check(self.AX)

    """ - ADC - Add with Carry """

    def adc(self, addr):
        if is_immediate(addr):
            print("ADC #$" + hfmt(self.ram.read(addr)))
        else:
            print("ADC $" + hfmt(addr))

        val = self.ram.read(addr)
        result, carry = badd(self.AX, val, check_bit(self.flags, 0))

        # Set carry flag
        if carry:
            self.flags = set_bit(self.flags, 0, 1)
        else:
            self.flags = set_bit(self.flags, 0, 0)
        # Set zero flag
        self.zero_check(result)
        # Set overflow flag
        self.overflow_check(self.AX, val)
        # Set negative flag
        self.negative_check(result)

        self.AX = result

    """ - BIT - Bit Test """

    # Zero Page
    def bit(self, addr):
        print("BIT $" + hfmt(addr))

        val = self.ram.read(addr)

        if self.AX & val == 0:
            self.flags = set_bit(self.flags, 1, 1)
        else:
            self.flags = set_bit(self.flags, 1, 0)

        self.flags = set_bit(self.flags, 6, check_bit(val, 6))
        self.flags = set_bit(self.flags, 7, check_bit(val, 7))

    """ - BRK - Force Interrupt """

    def brk(self):
        print("BRK")

        # Push program counter and processor status
        self.ram.push(self.PC, self.SP)
        self.SP -= 1

        self.ram.push(self.flags, self.SP)
        self.SP -= 1

        # IRQ interrupt vector at $FFFE/F is loaded into the PC
        # |
        # v
        # self.PC = self.ram.read(0xFFFE)

        # Set break flag
        self.flags = set_bit(self.flags, 4, 1)

    """ - BCC - Branch if Carry Clear """

    def bcc(self, addr):
        print("BCC $" + hfmt(addr))

        # If carry bit is clear add relative displacement
        if not check_bit(self.flags, 0):
            # If number is negative subtract it's two's complement
            if check_bit(addr, 7):
                num = decomp(addr)
                self.PC -= (num + 1)
            # If number is positive add it to the program counter
            else:
                self.PC += (addr + 1)

    """ - BCS - Branch if Carry Set """

    def bcs(self, addr):
        print("BCS $" + hfmt(addr))

        # If carry bit is set add relative displacement
        if check_bit(self.flags, 0):
            # If number is negative subtract it's two's complement
            if check_bit(addr, 7):
                num = decomp(addr)
                self.PC -= (num + 1)
            # If number is positive add it to the program counter
            else:
                self.PC += (addr + 1)

    """ - BEQ - Branch if Equal """

    def beq(self, addr):
        print("BEQ $" + hfmt(addr))

        # If zero bit is set add relative displacement
        if check_bit(self.flags, 1):
            # If number is negative subtract it's two's complement
            if check_bit(addr, 7):
                num = decomp(addr)
                self.PC -= (num + 1)
            # If number is positive add it to the program counter
            else:
                self.PC += (addr + 1)

    """ - BNE - Branch if Not Equal """

    def bne(self, addr):
        addr = self.ram.read(addr)

        print("BNE $" + hfmt(addr))

        # If zero bit is clear add relative displacement
        if not check_bit(self.flags, 1):
            # If number is negative subtract it's two's complement
            if check_bit(addr, 7):
                num = decomp(addr)
                self.PC -= (num + 1)
            # If number is positive add it to the program counter
            else:
                self.PC += (addr + 1)

    """ - BPL - Branch if Positive """

    def bpl(self, addr):
        print("BPL $" + hfmt(addr))

        # If negative bit is clear add relative displacement
        if not check_bit(self.flags, 7):
            # If number is negative subtract it's two's complement
            if check_bit(addr, 7):
                num = decomp(addr)
                self.PC -= (num + 1)
            # If number is positive add it to the program counter
            else:
                self.PC += (addr + 1)

    """ - CLC - Clear Carry Flag """

    def clc(self):
        print("CLC")

        self.flags = set_bit(self.flags, 0, 0)

    """ - CMP - Compare """

    def cmp(self, addr):
        if is_immediate(addr):
            print("CMP #$" + hfmt(self.ram.read(addr)))
        else:
            print("CMP $" + hfmt(addr))

        val = self.ram.read(addr)
        result = bsub(self.AX, val)[0]

        # If value of AX is greater than passed value, set carry flag
        if self.AX >= val:
            self.flags = set_bit(self.flags, 0, 1)
        else:
            self.flags = set_bit(self.flags, 0, 0)

        # If value of AX is equal to passed value, set zero flag
        if self.AX == val:
            self.flags = set_bit(self.flags, 1, 1)
        else:
            self.flags = set_bit(self.flags, 1, 0)

        # If bit 7 of the result is set, set negative flag
        self.flags = set_bit(self.flags, 7, check_bit(result, 7))

    """ - CPX - Compare X Register """

    def cpx(self, addr):
        if is_immediate(addr):
            print("CPX #$" + hfmt(self.ram.read(addr)))
        else:
            print("CPX $" + hfmt(addr))

        val = self.ram.read(addr)

        result = bsub(self.X, val)[0]

        # If value of AX is greater than passed value, set carry flag
        if self.X >= val:
            self.flags = set_bit(self.flags, 0, 1)
        else:
            self.flags = set_bit(self.flags, 0, 0)

        # If value of AX is equal to passed value, set zero flag
        if self.X == val:
            self.flags = set_bit(self.flags, 1, 1)
        else:
            self.flags = set_bit(self.flags, 1, 0)

        # If bit 7 of the result is set, set negative flag
        self.flags = set_bit(self.flags, 7, check_bit(result, 7))

    """ - CPY - Compare Y Register """

    def cpy(self, addr):
        if is_immediate(addr):
            print("CPY #$" + hfmt(self.ram.read(addr)))
        else:
            print("CPY $" + hfmt(addr))

        val = self.ram.read(addr)

        result = bsub(self.Y, val)[0]

        # If value of AX is greater than passed value, set carry flag
        if self.Y >= val:
            self.flags = set_bit(self.flags, 0, 1)
        else:
            self.flags = set_bit(self.flags, 0, 0)

        # If value of AX is equal to passed value, set zero flag
        if self.Y == val:
            self.flags = set_bit(self.flags, 1, 1)
        else:
            self.flags = set_bit(self.flags, 1, 0)

        # If bit 7 of the result is set, set negative flag
        self.flags = set_bit(self.flags, 7, check_bit(result, 7))

    """ - JMP - Jump """

    def jmp(self, addr):
        print("JMP $" + hfmt(addr))

        self.PC = addr - 1

        self.offset = 0

    """ - JSR - Jump to Subroutine """

    def jsr(self, addr):
        print("JSR $" + hfmt(addr))

        # Push upper byte of program counter to stack
        self.ram.push(self.PC >> 8, self.SP - 1)
        # Push lower byte of program counter to stack
        self.ram.push((self.PC + 2) & 0xFF, self.SP)

        self.SP -= 2

        self.PC = addr - 3

    """ - RTS - Return from Subroutine """

    def rts(self):
        print("RTS")

        self.SP += 2
        self.PC = hcat(self.ram.pop(self.SP - 1), self.ram.pop(self.SP))

    """ - PHA - Push Accumulator """

    def pha(self):
        print("PHA")

        self.ram.push(self.AX, self.SP)
        self.SP -= 1

    """ - PLA - Pull Accumulator """

    def pla(self):
        print("PLA")

        self.SP += 1
        self.AX = self.ram.pop(self.SP)