Skip to content

CPU

CPU of the Emulator.

Source code in core/cpu.py
class CPU:
    """
    CPU of the Emulator.
    """

    __slots__ = (
        "DT",
        "I",
        "PC",
        "ST",
        "V",
        "display",
        "halt",
        "keypad",
        "memory",
        "op",
        "sound",
        "stack",
        "sync",
    )

    def __init__(self, display: Display, memory: Memory, keypad: Keypad) -> None:
        """
        CPU construtor.

        Args:
            memory: Primary Memory of size 4096 bytes.
            display: Display Handler for rendering sprites.
            keypad: 16-key hexadecimal keypad for taking input.

        Attributes:
            sound (pygame.mixer.Sound): A [pygame.mixer.Sound](https://www.pygame.org/docs/ref/mixer.html#pygame.mixer.Sound) object.
            op (Opcode): Opcode for identifying operation.

            V (List[int]): 16 General Purpose 8-bit registers.
            I (int): 16-bit Index register to store memory locations.
            DT (int): 8-bit Delay Timer.
            ST (int): 8-bit Sound Timer.
            PC (int): 16-bit register to store the currently executing address.
            stack (List[int]): Array of 16 16-bit values for nesting subroutines.

            halt (bool): Flag to check if the CPU is halted.
            sync (bool): Flag to sync the display with the timer.
        """
        # devices
        self.display = display
        self.memory = memory
        self.keypad = keypad
        self.sound: pygame.mixer.Sound = pygame.mixer.Sound(
            files("rechip8.static").joinpath("beep.mp3").open("rb")
        )
        self.op: Opcode = Opcode(inst=0x0000)

        # registers
        self.V: t.List[int] = [0] * 16
        self.I: int = 0
        self.DT: int = 0
        self.ST: int = 0
        self.PC: int = INIT_LOC_CONSTANT
        self.stack: t.List[int] = [0] * 16

        # flags
        self.halt: bool = False
        self.sync: bool = False

    def SYS_addr(self) -> None:
        """
        $0nnn - Jump to a machine code routine at nnn. (Edit: use kk instead)
        """
        optab = {0xE0: self.CLS, 0xEE: self.RET}
        optab[self.op.kk]()

    def CLS(self) -> None:
        """
        $00E0 - Clear the display.
        """
        self.display.clear()

    def RET(self) -> None:
        """
        $00EE - Return from a subroutine.
        """
        self.PC = self.stack.pop()

    def JP_addr(self) -> None:
        """
        $1nnn - Jump to location nnn. (PC = nnn)
        """
        self.PC = self.op.nnn

    def CALL_addr(self) -> None:
        """
        $2nnn - Call subroutine at nnn.
        """
        self.stack.append(self.PC)
        self.PC = self.op.nnn

    def SE_Vx_byte(self) -> None:
        """
        $3xkk - Skip next instruction if Vx = kk.
        """
        if self.V[self.op.x] == self.op.kk:
            self.PC += 2

    def SNE_Vx_byte(self) -> None:
        """
        $4xkk - Skip next instruction if Vx != kk.
        """
        if self.V[self.op.x] != self.op.kk:
            self.PC += 2

    def SE_Vx_Vy(self) -> None:
        """
        $5xy0 - Skip next instruction if Vx = Vy.
        """
        if self.V[self.op.x] == self.V[self.op.y]:
            self.PC += 2

    def LD_Vx_byte(self) -> None:
        """
        $6xkk - Set Vx = kk.
        """
        self.V[self.op.x] = self.op.kk

    def ADD_Vx_byte(self) -> None:
        """
        $7xkk - Set Vx = Vx + kk.
        """
        self.V[self.op.x] = (self.V[self.op.x] + self.op.kk) & 0xFF

    def JP_addr_8(self) -> None:
        """
        $8xyn -  Jump to a machine code routine at n.
        """
        optab = {
            0x0: self.LD_Vx_Vy,
            0x1: self.OR_Vx_Vy,
            0x2: self.AND_Vx_Vy,
            0x3: self.XOR_Vx_Vy,
            0x4: self.ADD_Vx_Vy,
            0x5: self.SUB_Vx_Vy,
            0x6: self.SHR_Vx_Vy,
            0x7: self.SUBN_Vx_Vy,
            0xE: self.SHL_Vx_Vy,
        }
        optab[self.op.n]()

    def LD_Vx_Vy(self) -> None:
        """
        $8xy0 - Set Vx = Vy.
        """
        self.V[self.op.x] = (self.V[self.op.y]) & 0xFF

    def OR_Vx_Vy(self) -> None:
        """
        $8xy1 - Set Vx = Vx OR Vy.
        """
        self.V[self.op.x] = (self.V[self.op.x] | self.V[self.op.y]) & 0xFF
        self.V[0xF] = 0

    def AND_Vx_Vy(self) -> None:
        """
        $8xy2 - Set Vx = Vx AND Vy.
        """
        self.V[self.op.x] = (self.V[self.op.x] & self.V[self.op.y]) & 0xFF
        self.V[0xF] = 0

    def XOR_Vx_Vy(self) -> None:
        """
        $8xy3 - Set Vx = Vx XOR Vy.
        """
        self.V[self.op.x] = (self.V[self.op.x] ^ self.V[self.op.y]) & 0xFF
        self.V[0xF] = 0

    def ADD_Vx_Vy(self) -> None:
        """
        $8xy4 - Set Vx = Vx + Vy, set VF = carry.
        """
        val = self.V[self.op.x] + self.V[self.op.y]
        self.V[self.op.x] = val & 0xFF

        if val > 0xFF:
            self.V[0xF] = 1
        else:
            self.V[0xF] = 0

    def SUB_Vx_Vy(self) -> None:
        """
        $8xy5 - Set Vx = Vx - Vy, set VF = NOT borrow.
        """
        x = self.V[self.op.x]
        y = self.V[self.op.y]

        self.V[self.op.x] = (x - y) & 0xFF

        if x < y:
            self.V[0xF] = 0
        else:
            self.V[0xF] = 1

    def SHR_Vx_Vy(self) -> None:
        """
        $8xy6 - Set Vx = Vx SHR 1.
        """
        prev = self.V[self.op.x]
        self.V[self.op.x] = self.V[self.op.y]
        self.V[self.op.x] = (self.V[self.op.x] >> 1) & 0xFF
        self.V[0xF] = prev & 1

    def SUBN_Vx_Vy(self) -> None:
        """
        $8xy7 - Set Vx = Vy - Vx, set VF = NOT borrow.
        """
        self.V[self.op.x] = (self.V[self.op.y] - self.V[self.op.x]) & 0xFF
        if self.V[self.op.x] > self.V[self.op.y]:
            self.V[0xF] = 0
        else:
            self.V[0xF] = 1

    def SHL_Vx_Vy(self) -> None:
        """
        $8xyE - Set Vx = Vx SHL 1.
        """
        y = self.V[self.op.y]

        self.V[self.op.x] = (y << 1) & 0xFF
        self.V[0xF] = (y & 0xFF) >> 7

    def SNE_Vx_Vy(self) -> None:
        """
        $9xy0 - Skip next instruction if Vx != Vy.
        """
        if self.V[self.op.x] != self.V[self.op.y]:
            self.PC += 2

    def LD_I_addr(self) -> None:
        """
        $Annn - Set I = nnn.
        """
        self.I = self.op.nnn

    def JP_V0_addr(self) -> None:
        """
        $Bnnn - Jump to location nnn + V0.
        """
        self.PC = self.op.nnn + self.V[0]

    def RND_Vx_byte(self) -> None:
        """
        $Cxkk - Set Vx = random byte AND kk.
        """
        self.V[self.op.x] = randint(0, 0xFF) & self.op.kk

    def DRW_Vx_Vy_nibble(self) -> None:
        """
        $Dxyn - Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision. :)
        """
        if not self.sync:
            self.PC -= 2
            return

        x = self.V[self.op.x] % COLUMNS
        y = self.V[self.op.y] % ROWS

        self.V[0xF] = 0

        for i in range(self.op.n):
            sprite = self.memory.space[self.I + i]
            if (y + i) <= ROWS:
                for j in range(8):
                    if (x + j) <= COLUMNS:
                        px = (sprite >> (7 - j)) & 1
                        index = self.display.wrap(x + j, y + i)

                        if px == 1 and self.display.buffer[index] == 1:
                            self.V[0xF] = 1

                        self.display.buffer[index] ^= px

        self.sync = False

    def Jp_addr_E(self) -> None:
        """
        $Exkk - Jump to a machine code routine at kk.
        """
        optab = {0x9E: self.SKP_Vx, 0xA1: self.SKNP_Vx}
        optab[self.op.kk]()

    def SKP_Vx(self) -> None:
        """
        $Ex9E - Skip next instruction if key with the value of Vx is pressed.
        """
        if self.keypad.state[self.V[self.op.x] & 0xF]:
            self.PC += 2

    def SKNP_Vx(self) -> None:
        """
        $ExA1 - Skip next instruction if key with the value of Vx is not pressed.
        """
        if not self.keypad.state[self.V[self.op.x] & 0xF]:
            self.PC += 2

    def Jp_addr_F(self) -> None:
        """
        $Fxkk - Jump to a machine code routine at kk.
        """
        optab = {
            0x07: self.LD_Vx_DT,
            0x0A: self.LD_Vx_K,
            0x15: self.LD_DT_Vx,
            0x18: self.LD_ST_Vx,
            0x1E: self.ADD_I_Vx,
            0x29: self.LD_F_Vx,
            0x33: self.LD_B_Vx,
            0x55: self.LD_I_Vx,
            0x65: self.LD_Vx_I,
        }
        optab[self.op.kk]()

    def LD_Vx_DT(self) -> None:
        """
        $Fx07 - Set Vx = delay timer value.
        """
        self.V[self.op.x] = self.DT

    def LD_Vx_K(self) -> None:
        """
        $Fx0A - Wait for a key press, store the value of the key in Vx.
        """
        self.halt = True

    def LD_DT_Vx(self) -> None:
        """
        $Fx15 - Set delay timer = Vx.
        """
        self.DT = self.V[self.op.x]

    def LD_ST_Vx(self) -> None:
        """
        $Fx18 - Set sound timer = Vx.
        """
        self.ST = self.V[self.op.x]

    def ADD_I_Vx(self) -> None:
        """
        $Fx1E - Set I = I + Vx.
        """
        self.I += self.V[self.op.x]

    def LD_F_Vx(self) -> None:
        """
        $Fx29 - Set I = location of sprite for digit Vx.
        """
        self.I = (self.V[self.op.x] * 5) & 0x0FFF

    def LD_B_Vx(self) -> None:
        """
        $Fx33 - Store BCD representation of Vx in memory locations I, I+1, and I+2.
        """
        self.memory.space[self.I] = self.V[self.op.x] // 100
        self.memory.space[self.I + 1] = (self.V[self.op.x] % 100) // 10
        self.memory.space[self.I + 2] = self.V[self.op.x] % 10

    def LD_I_Vx(self) -> None:
        """
        $Fx55 - Store registers V0 through Vx in memory starting at location I.
        """
        for i in range(self.op.x + 1):
            self.memory.space[self.I + i] = self.V[i]
        self.I += self.op.x + 1

    def LD_Vx_I(self) -> None:
        """
        $Fx65 - Read registers V0 through Vx from memory starting at location I.
        """
        for i in range(self.op.x + 1):
            self.V[i] = self.memory.space[self.I + i]
        self.I += self.op.x + 1

    @property
    def optable(self) -> t.Mapping[int, t.Callable[..., None]]:
        """
        Opcode function lookup table.

        Returns:
            dict: opcode.type:subroutine mapping.
        """
        return {
            0x0: self.SYS_addr,
            0x1: self.JP_addr,
            0x2: self.CALL_addr,
            0x3: self.SE_Vx_byte,
            0x4: self.SNE_Vx_byte,
            0x5: self.SE_Vx_Vy,
            0x6: self.LD_Vx_byte,
            0x7: self.ADD_Vx_byte,
            0x8: self.JP_addr_8,
            0x9: self.SNE_Vx_Vy,
            0xA: self.LD_I_addr,
            0xB: self.JP_V0_addr,
            0xC: self.RND_Vx_byte,
            0xD: self.DRW_Vx_Vy_nibble,
            0xE: self.Jp_addr_E,
            0xF: self.Jp_addr_F,
        }

    def beep(self) -> None:
        """
        Make a beep sound.
        """
        self.sound.play()

    def cycle(self) -> None:
        """
        Fetch, Decode and Execute instructions from the memory.
        """
        fetch = (self.memory.space[self.PC] << 8) | self.memory.space[self.PC + 1]
        self.op = Opcode(fetch)
        self.PC += 2

        try:
            self.optable[(self.op.type & 0xF000) >> 12]()
        except KeyError:
            logging.error(f"{CROSS} Opcode not found: {hex(self.op.inst)}")
            self.display.destroy()

    def handle_timers(self) -> None:
        """
        Decrement ST and DT.
        """
        if self.DT > 0:
            self.DT -= 1

        if self.ST > 0:
            self.ST = 0
            self.beep()

optable: Mapping[int, Callable[..., NoneType]] property readonly +

Opcode function lookup table.

Returns:

Type Description
dict

opcode.type:subroutine mapping.

__init__(self, display, memory, keypad) special +

CPU construtor.

Parameters:

Name Type Description Default
memory Memory

Primary Memory of size 4096 bytes.

required
display Display

Display Handler for rendering sprites.

required
keypad Keypad

16-key hexadecimal keypad for taking input.

required

Attributes:

Name Type Description
sound pygame.mixer.Sound

A pygame.mixer.Sound object.

op Opcode

Opcode for identifying operation.

V List[int]

16 General Purpose 8-bit registers.

I int

16-bit Index register to store memory locations.

DT int

8-bit Delay Timer.

ST int

8-bit Sound Timer.

PC int

16-bit register to store the currently executing address.

stack List[int]

Array of 16 16-bit values for nesting subroutines.

halt bool

Flag to check if the CPU is halted.

sync bool

Flag to sync the display with the timer.

Source code in core/cpu.py
def __init__(self, display: Display, memory: Memory, keypad: Keypad) -> None:
    """
    CPU construtor.

    Args:
        memory: Primary Memory of size 4096 bytes.
        display: Display Handler for rendering sprites.
        keypad: 16-key hexadecimal keypad for taking input.

    Attributes:
        sound (pygame.mixer.Sound): A [pygame.mixer.Sound](https://www.pygame.org/docs/ref/mixer.html#pygame.mixer.Sound) object.
        op (Opcode): Opcode for identifying operation.

        V (List[int]): 16 General Purpose 8-bit registers.
        I (int): 16-bit Index register to store memory locations.
        DT (int): 8-bit Delay Timer.
        ST (int): 8-bit Sound Timer.
        PC (int): 16-bit register to store the currently executing address.
        stack (List[int]): Array of 16 16-bit values for nesting subroutines.

        halt (bool): Flag to check if the CPU is halted.
        sync (bool): Flag to sync the display with the timer.
    """
    # devices
    self.display = display
    self.memory = memory
    self.keypad = keypad
    self.sound: pygame.mixer.Sound = pygame.mixer.Sound(
        files("rechip8.static").joinpath("beep.mp3").open("rb")
    )
    self.op: Opcode = Opcode(inst=0x0000)

    # registers
    self.V: t.List[int] = [0] * 16
    self.I: int = 0
    self.DT: int = 0
    self.ST: int = 0
    self.PC: int = INIT_LOC_CONSTANT
    self.stack: t.List[int] = [0] * 16

    # flags
    self.halt: bool = False
    self.sync: bool = False

SYS_addr(self) +

$0nnn - Jump to a machine code routine at nnn. (Edit: use kk instead)

Source code in core/cpu.py
def SYS_addr(self) -> None:
    """
    $0nnn - Jump to a machine code routine at nnn. (Edit: use kk instead)
    """
    optab = {0xE0: self.CLS, 0xEE: self.RET}
    optab[self.op.kk]()

CLS(self) +

$00E0 - Clear the display.

Source code in core/cpu.py
def CLS(self) -> None:
    """
    $00E0 - Clear the display.
    """
    self.display.clear()

RET(self) +

$00EE - Return from a subroutine.

Source code in core/cpu.py
def RET(self) -> None:
    """
    $00EE - Return from a subroutine.
    """
    self.PC = self.stack.pop()

JP_addr(self) +

$1nnn - Jump to location nnn. (PC = nnn)

Source code in core/cpu.py
def JP_addr(self) -> None:
    """
    $1nnn - Jump to location nnn. (PC = nnn)
    """
    self.PC = self.op.nnn

CALL_addr(self) +

$2nnn - Call subroutine at nnn.

Source code in core/cpu.py
def CALL_addr(self) -> None:
    """
    $2nnn - Call subroutine at nnn.
    """
    self.stack.append(self.PC)
    self.PC = self.op.nnn

SE_Vx_byte(self) +

$3xkk - Skip next instruction if Vx = kk.

Source code in core/cpu.py
def SE_Vx_byte(self) -> None:
    """
    $3xkk - Skip next instruction if Vx = kk.
    """
    if self.V[self.op.x] == self.op.kk:
        self.PC += 2

SNE_Vx_byte(self) +

$4xkk - Skip next instruction if Vx != kk.

Source code in core/cpu.py
def SNE_Vx_byte(self) -> None:
    """
    $4xkk - Skip next instruction if Vx != kk.
    """
    if self.V[self.op.x] != self.op.kk:
        self.PC += 2

SE_Vx_Vy(self) +

$5xy0 - Skip next instruction if Vx = Vy.

Source code in core/cpu.py
def SE_Vx_Vy(self) -> None:
    """
    $5xy0 - Skip next instruction if Vx = Vy.
    """
    if self.V[self.op.x] == self.V[self.op.y]:
        self.PC += 2

LD_Vx_byte(self) +

$6xkk - Set Vx = kk.

Source code in core/cpu.py
def LD_Vx_byte(self) -> None:
    """
    $6xkk - Set Vx = kk.
    """
    self.V[self.op.x] = self.op.kk

ADD_Vx_byte(self) +

$7xkk - Set Vx = Vx + kk.

Source code in core/cpu.py
def ADD_Vx_byte(self) -> None:
    """
    $7xkk - Set Vx = Vx + kk.
    """
    self.V[self.op.x] = (self.V[self.op.x] + self.op.kk) & 0xFF

JP_addr_8(self) +

$8xyn - Jump to a machine code routine at n.

Source code in core/cpu.py
def JP_addr_8(self) -> None:
    """
    $8xyn -  Jump to a machine code routine at n.
    """
    optab = {
        0x0: self.LD_Vx_Vy,
        0x1: self.OR_Vx_Vy,
        0x2: self.AND_Vx_Vy,
        0x3: self.XOR_Vx_Vy,
        0x4: self.ADD_Vx_Vy,
        0x5: self.SUB_Vx_Vy,
        0x6: self.SHR_Vx_Vy,
        0x7: self.SUBN_Vx_Vy,
        0xE: self.SHL_Vx_Vy,
    }
    optab[self.op.n]()

LD_Vx_Vy(self) +

$8xy0 - Set Vx = Vy.

Source code in core/cpu.py
def LD_Vx_Vy(self) -> None:
    """
    $8xy0 - Set Vx = Vy.
    """
    self.V[self.op.x] = (self.V[self.op.y]) & 0xFF

OR_Vx_Vy(self) +

$8xy1 - Set Vx = Vx OR Vy.

Source code in core/cpu.py
def OR_Vx_Vy(self) -> None:
    """
    $8xy1 - Set Vx = Vx OR Vy.
    """
    self.V[self.op.x] = (self.V[self.op.x] | self.V[self.op.y]) & 0xFF
    self.V[0xF] = 0

AND_Vx_Vy(self) +

$8xy2 - Set Vx = Vx AND Vy.

Source code in core/cpu.py
def AND_Vx_Vy(self) -> None:
    """
    $8xy2 - Set Vx = Vx AND Vy.
    """
    self.V[self.op.x] = (self.V[self.op.x] & self.V[self.op.y]) & 0xFF
    self.V[0xF] = 0

XOR_Vx_Vy(self) +

$8xy3 - Set Vx = Vx XOR Vy.

Source code in core/cpu.py
def XOR_Vx_Vy(self) -> None:
    """
    $8xy3 - Set Vx = Vx XOR Vy.
    """
    self.V[self.op.x] = (self.V[self.op.x] ^ self.V[self.op.y]) & 0xFF
    self.V[0xF] = 0

ADD_Vx_Vy(self) +

$8xy4 - Set Vx = Vx + Vy, set VF = carry.

Source code in core/cpu.py
def ADD_Vx_Vy(self) -> None:
    """
    $8xy4 - Set Vx = Vx + Vy, set VF = carry.
    """
    val = self.V[self.op.x] + self.V[self.op.y]
    self.V[self.op.x] = val & 0xFF

    if val > 0xFF:
        self.V[0xF] = 1
    else:
        self.V[0xF] = 0

SUB_Vx_Vy(self) +

$8xy5 - Set Vx = Vx - Vy, set VF = NOT borrow.

Source code in core/cpu.py
def SUB_Vx_Vy(self) -> None:
    """
    $8xy5 - Set Vx = Vx - Vy, set VF = NOT borrow.
    """
    x = self.V[self.op.x]
    y = self.V[self.op.y]

    self.V[self.op.x] = (x - y) & 0xFF

    if x < y:
        self.V[0xF] = 0
    else:
        self.V[0xF] = 1

SHR_Vx_Vy(self) +

$8xy6 - Set Vx = Vx SHR 1.

Source code in core/cpu.py
def SHR_Vx_Vy(self) -> None:
    """
    $8xy6 - Set Vx = Vx SHR 1.
    """
    prev = self.V[self.op.x]
    self.V[self.op.x] = self.V[self.op.y]
    self.V[self.op.x] = (self.V[self.op.x] >> 1) & 0xFF
    self.V[0xF] = prev & 1

SUBN_Vx_Vy(self) +

$8xy7 - Set Vx = Vy - Vx, set VF = NOT borrow.

Source code in core/cpu.py
def SUBN_Vx_Vy(self) -> None:
    """
    $8xy7 - Set Vx = Vy - Vx, set VF = NOT borrow.
    """
    self.V[self.op.x] = (self.V[self.op.y] - self.V[self.op.x]) & 0xFF
    if self.V[self.op.x] > self.V[self.op.y]:
        self.V[0xF] = 0
    else:
        self.V[0xF] = 1

SHL_Vx_Vy(self) +

$8xyE - Set Vx = Vx SHL 1.

Source code in core/cpu.py
def SHL_Vx_Vy(self) -> None:
    """
    $8xyE - Set Vx = Vx SHL 1.
    """
    y = self.V[self.op.y]

    self.V[self.op.x] = (y << 1) & 0xFF
    self.V[0xF] = (y & 0xFF) >> 7

SNE_Vx_Vy(self) +

$9xy0 - Skip next instruction if Vx != Vy.

Source code in core/cpu.py
def SNE_Vx_Vy(self) -> None:
    """
    $9xy0 - Skip next instruction if Vx != Vy.
    """
    if self.V[self.op.x] != self.V[self.op.y]:
        self.PC += 2

LD_I_addr(self) +

$Annn - Set I = nnn.

Source code in core/cpu.py
def LD_I_addr(self) -> None:
    """
    $Annn - Set I = nnn.
    """
    self.I = self.op.nnn

JP_V0_addr(self) +

$Bnnn - Jump to location nnn + V0.

Source code in core/cpu.py
def JP_V0_addr(self) -> None:
    """
    $Bnnn - Jump to location nnn + V0.
    """
    self.PC = self.op.nnn + self.V[0]

RND_Vx_byte(self) +

$Cxkk - Set Vx = random byte AND kk.

Source code in core/cpu.py
def RND_Vx_byte(self) -> None:
    """
    $Cxkk - Set Vx = random byte AND kk.
    """
    self.V[self.op.x] = randint(0, 0xFF) & self.op.kk

DRW_Vx_Vy_nibble(self) +

$Dxyn - Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision. :)

Source code in core/cpu.py
def DRW_Vx_Vy_nibble(self) -> None:
    """
    $Dxyn - Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision. :)
    """
    if not self.sync:
        self.PC -= 2
        return

    x = self.V[self.op.x] % COLUMNS
    y = self.V[self.op.y] % ROWS

    self.V[0xF] = 0

    for i in range(self.op.n):
        sprite = self.memory.space[self.I + i]
        if (y + i) <= ROWS:
            for j in range(8):
                if (x + j) <= COLUMNS:
                    px = (sprite >> (7 - j)) & 1
                    index = self.display.wrap(x + j, y + i)

                    if px == 1 and self.display.buffer[index] == 1:
                        self.V[0xF] = 1

                    self.display.buffer[index] ^= px

    self.sync = False

Jp_addr_E(self) +

$Exkk - Jump to a machine code routine at kk.

Source code in core/cpu.py
def Jp_addr_E(self) -> None:
    """
    $Exkk - Jump to a machine code routine at kk.
    """
    optab = {0x9E: self.SKP_Vx, 0xA1: self.SKNP_Vx}
    optab[self.op.kk]()

SKP_Vx(self) +

$Ex9E - Skip next instruction if key with the value of Vx is pressed.

Source code in core/cpu.py
def SKP_Vx(self) -> None:
    """
    $Ex9E - Skip next instruction if key with the value of Vx is pressed.
    """
    if self.keypad.state[self.V[self.op.x] & 0xF]:
        self.PC += 2

SKNP_Vx(self) +

$ExA1 - Skip next instruction if key with the value of Vx is not pressed.

Source code in core/cpu.py
def SKNP_Vx(self) -> None:
    """
    $ExA1 - Skip next instruction if key with the value of Vx is not pressed.
    """
    if not self.keypad.state[self.V[self.op.x] & 0xF]:
        self.PC += 2

Jp_addr_F(self) +

$Fxkk - Jump to a machine code routine at kk.

Source code in core/cpu.py
def Jp_addr_F(self) -> None:
    """
    $Fxkk - Jump to a machine code routine at kk.
    """
    optab = {
        0x07: self.LD_Vx_DT,
        0x0A: self.LD_Vx_K,
        0x15: self.LD_DT_Vx,
        0x18: self.LD_ST_Vx,
        0x1E: self.ADD_I_Vx,
        0x29: self.LD_F_Vx,
        0x33: self.LD_B_Vx,
        0x55: self.LD_I_Vx,
        0x65: self.LD_Vx_I,
    }
    optab[self.op.kk]()

LD_Vx_DT(self) +

$Fx07 - Set Vx = delay timer value.

Source code in core/cpu.py
def LD_Vx_DT(self) -> None:
    """
    $Fx07 - Set Vx = delay timer value.
    """
    self.V[self.op.x] = self.DT

LD_Vx_K(self) +

$Fx0A - Wait for a key press, store the value of the key in Vx.

Source code in core/cpu.py
def LD_Vx_K(self) -> None:
    """
    $Fx0A - Wait for a key press, store the value of the key in Vx.
    """
    self.halt = True

LD_DT_Vx(self) +

$Fx15 - Set delay timer = Vx.

Source code in core/cpu.py
def LD_DT_Vx(self) -> None:
    """
    $Fx15 - Set delay timer = Vx.
    """
    self.DT = self.V[self.op.x]

LD_ST_Vx(self) +

$Fx18 - Set sound timer = Vx.

Source code in core/cpu.py
def LD_ST_Vx(self) -> None:
    """
    $Fx18 - Set sound timer = Vx.
    """
    self.ST = self.V[self.op.x]

ADD_I_Vx(self) +

$Fx1E - Set I = I + Vx.

Source code in core/cpu.py
def ADD_I_Vx(self) -> None:
    """
    $Fx1E - Set I = I + Vx.
    """
    self.I += self.V[self.op.x]

LD_F_Vx(self) +

$Fx29 - Set I = location of sprite for digit Vx.

Source code in core/cpu.py
def LD_F_Vx(self) -> None:
    """
    $Fx29 - Set I = location of sprite for digit Vx.
    """
    self.I = (self.V[self.op.x] * 5) & 0x0FFF

LD_B_Vx(self) +

$Fx33 - Store BCD representation of Vx in memory locations I, I+1, and I+2.

Source code in core/cpu.py
def LD_B_Vx(self) -> None:
    """
    $Fx33 - Store BCD representation of Vx in memory locations I, I+1, and I+2.
    """
    self.memory.space[self.I] = self.V[self.op.x] // 100
    self.memory.space[self.I + 1] = (self.V[self.op.x] % 100) // 10
    self.memory.space[self.I + 2] = self.V[self.op.x] % 10

LD_I_Vx(self) +

$Fx55 - Store registers V0 through Vx in memory starting at location I.

Source code in core/cpu.py
def LD_I_Vx(self) -> None:
    """
    $Fx55 - Store registers V0 through Vx in memory starting at location I.
    """
    for i in range(self.op.x + 1):
        self.memory.space[self.I + i] = self.V[i]
    self.I += self.op.x + 1

LD_Vx_I(self) +

$Fx65 - Read registers V0 through Vx from memory starting at location I.

Source code in core/cpu.py
def LD_Vx_I(self) -> None:
    """
    $Fx65 - Read registers V0 through Vx from memory starting at location I.
    """
    for i in range(self.op.x + 1):
        self.V[i] = self.memory.space[self.I + i]
    self.I += self.op.x + 1

beep(self) +

Make a beep sound.

Source code in core/cpu.py
def beep(self) -> None:
    """
    Make a beep sound.
    """
    self.sound.play()

cycle(self) +

Fetch, Decode and Execute instructions from the memory.

Source code in core/cpu.py
def cycle(self) -> None:
    """
    Fetch, Decode and Execute instructions from the memory.
    """
    fetch = (self.memory.space[self.PC] << 8) | self.memory.space[self.PC + 1]
    self.op = Opcode(fetch)
    self.PC += 2

    try:
        self.optable[(self.op.type & 0xF000) >> 12]()
    except KeyError:
        logging.error(f"{CROSS} Opcode not found: {hex(self.op.inst)}")
        self.display.destroy()

handle_timers(self) +

Decrement ST and DT.

Source code in core/cpu.py
def handle_timers(self) -> None:
    """
    Decrement ST and DT.
    """
    if self.DT > 0:
        self.DT -= 1

    if self.ST > 0:
        self.ST = 0
        self.beep()