Skip to content
Snippets Groups Projects
iop.cpp 28.2 KiB
Newer Older
Geo Ster's avatar
Geo Ster committed
#include <cpu/iop/iop.hpp>
#include <cstring>
#include <fmt/color.h>

#ifndef NDEBUG
#define log(x) (void)0
#else
constexpr fmt::v8::text_style BOLD = fg(fmt::color::forest_green) | fmt::emphasis::bold;
#define log(...) fmt::print(disassembly, __VA_ARGS__)
#endif

namespace iop
{
    IOProcessor::IOProcessor(ComponentManager* manager) :
        manager(manager)
    {
        /* Set PRID Processor ID*/
        cop0.PRId = 0x1f;

        /* Open output log */
        disassembly = std::fopen("disassembly_iop.log", "w");

        /* Reset CPU state. */
        reset();
    }

    IOProcessor::~IOProcessor()
    {
        std::fclose(disassembly);
    }

    void IOProcessor::reset()
    {
        /* Reset registers and PC. */
        pc = 0xbfc00000;
        hi = 0; lo = 0;

        /* Add first instruction into the pipeline */
        direct_jump();

        /* Clear general purpose registers. */
        std::memset(gpr, 0, 32 * sizeof(uint32_t));
    }

    void IOProcessor::tick()
    {
        /* Fetch next instruction. */
        fetch();

        /* Execute it. */
        switch (instr.opcode)
        {
        case 0b000000: op_special(); break;
        case 0b000001: op_bcond(); break;
        case 0b001111: op_lui(); break;
        case 0b001101: op_ori(); break;
        case 0b101011: op_sw(); break;
        case 0b001001: op_addiu(); break;
        case 0b001000: op_addi(); break;
        case 0b000010: op_j(); break;
        case 0b010000: op_cop0(); break;
        case 0b100011: op_lw(); break;
        case 0b000101: op_bne(); break;
        case 0b101001: op_sh(); break;
        case 0b000011: op_jal(); break;
        case 0b001100: op_andi(); break;
        case 0b101000: op_sb(); break;
        case 0b100000: op_lb(); break;
        case 0b000100: op_beq(); break;
        case 0b000111: op_bgtz(); break;
        case 0b000110: op_blez(); break;
        case 0b100100: op_lbu(); break;
        case 0b001010: op_slti(); break;
        case 0b001011: op_sltiu(); break;
        case 0b100101: op_lhu(); break;
        case 0b100001: op_lh(); break;
        case 0b001110: op_xori(); break;
        case 0b101110: op_swr(); break;
        case 0b101010: op_swl(); break;
        case 0b100010: op_lwl(); break;
        case 0b100110: op_lwr(); break;
        default: exception(Exception::IllegalInstr);
        }

        /* Apply pending load delays. */
        handle_load_delay();
    }

    void IOProcessor::op_special()
    {
        switch (instr.r_type.funct)
        {
        case 0b000000: op_sll(); break;
        case 0b100101: op_or(); break;
        case 0b101011: op_sltu(); break;
        case 0b100001: op_addu(); break;
        case 0b001000: op_jr(); break;
        case 0b100100: op_and(); break;
        case 0b100000: op_add(); break;
        case 0b001001: op_jalr(); break;
        case 0b100011: op_subu(); break;
        case 0b000011: op_sra(); break;
        case 0b011010: op_div(); break;
        case 0b010010: op_mflo(); break;
        case 0b000010: op_srl(); break;
        case 0b011011: op_divu(); break;
        case 0b010000: op_mfhi(); break;
        case 0b101010: op_slt(); break;
        case 0b001100: op_syscall(); break;
        case 0b010011: op_mtlo(); break;
        case 0b010001: op_mthi(); break;
        case 0b000100: op_sllv(); break;
        case 0b100111: op_nor(); break;
        case 0b000111: op_srav(); break;
        case 0b000110: op_srlv(); break;
        case 0b011001: op_multu(); break;
        case 0b100110: op_xor(); break;
        case 0b001101: op_break(); break;
        case 0b011000: op_mult(); break;
        case 0b100010: op_sub(); break;
        default: exception(Exception::IllegalInstr);
        }
    }

    void IOProcessor::op_cop0()
    {
        switch (instr.i_type.rs)
        {
        case 0b00000: op_mfc0(); break;
        case 0b00100: op_mtc0(); break;
        case 0b10000: op_rfe(); break;
        default: exception(Exception::IllegalInstr);
        }
    }

    void IOProcessor::fetch()
    {
        /* Fetch instruction from main RAM. */
        instr = next_instr;

        /* Check aligment errors. */
Geo Ster's avatar
Geo Ster committed
        if (pc & 0x3) [[unlikely]]
Geo Ster's avatar
Geo Ster committed
        {
            cop0.BadA = pc;
            exception(Exception::ReadError);
            return;
        }

        /* Update PC. */
        direct_jump();

        log("PC: {:#x} instruction: {:#x} ", instr.pc, instr.value);
    }

    void IOProcessor::direct_jump()
    {
        next_instr = {};
        next_instr.value = read<uint32_t>(pc);
        next_instr.pc = pc;
        pc += 4;
    }

    void IOProcessor::exception(Exception cause, uint32_t cop)
    {
        fmt::print("[IOP] Exception of type {:d}\n", (int)cause);

        uint32_t mode = cop0.sr.value & 0x3F;
        cop0.sr.value &= ~(uint32_t)0x3F;
        cop0.sr.value |= (mode << 2) & 0x3F;

        uint32_t copy = cop0.cause.value & 0xff00;
        cop0.cause.exc_code = (uint32_t)cause;
        cop0.cause.CE = cop;
    
        bool is_delay_slot = instr.is_delay_slot;
        bool branch_taken = instr.branch_taken;
        if (cause == Exception::Interrupt) 
        {
            cop0.epc = next_instr.pc;

            /* Hack: related to the delay of the ex interrupt*/
            is_delay_slot = next_instr.is_delay_slot;
            branch_taken = next_instr.branch_taken;
        }
        else 
        {
            cop0.epc = instr.pc;
        }

        if (is_delay_slot) 
        {
            cop0.epc -= 4;

            cop0.cause.BD = true;
            cop0.TAR = next_instr.pc;

            if (branch_taken) 
            {
                cop0.cause.BT = true;
            }
        }

        /* Select exception address. */
        pc = exception_addr[cop0.sr.BEV];

        /* Bypass the pipeline and insert our instruction. */
        direct_jump();
    }

    void IOProcessor::handle_load_delay()
    {
        if (delayed_memory_load.reg != memory_load.reg) 
        {
            gpr[memory_load.reg] = memory_load.value;
        }
    
        memory_load = delayed_memory_load;
        delayed_memory_load.reg = 0;

        gpr[write_back.reg] = write_back.value;
        write_back.reg = 0;
        gpr[0] = 0;
    }

    void IOProcessor::op_bcond()
    {
        uint32_t rt = instr.i_type.rt;
        uint16_t rs = instr.i_type.rs;

        next_instr.is_delay_slot = true;

        bool should_link = (rt & 0x1E) == 0x10;
        bool should_branch = (int)(gpr[rs] ^ (rt << 31)) < 0;

        if (should_link) gpr[31] = instr.pc + 8;
        if (should_branch) branch();
    }

    void IOProcessor::op_swr()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t rs = instr.i_type.rs;
        int16_t imm = (int16_t)instr.i_type.immediate;

        uint32_t addr = gpr[rs] + imm;
        uint32_t aligned_addr = addr & 0xFFFFFFFC;
        uint32_t aligned_load = read<uint32_t>(aligned_addr);

        uint32_t value;
        switch (addr & 0x3)
        {
        case 0:
            value = gpr[rt];
            break;
        case 1:
            value = (aligned_load & 0x000000FF) | (gpr[rt] << 8);
            break;
        case 2:
            value = (aligned_load & 0x0000FFFF) | (gpr[rt] << 16);
            break;
        case 3:
            value = (aligned_load & 0x00FFFFFF) | (gpr[rt] << 24);
            break;
        }

        write<uint32_t>(aligned_addr, value);
    }

    void IOProcessor::op_swl()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t rs = instr.i_type.rs;
        int16_t imm = (int16_t)instr.i_type.immediate;

        uint32_t addr = gpr[rs] + imm;
        uint32_t aligned_addr = addr & 0xFFFFFFFC;
        uint32_t aligned_load = read<uint32_t>(aligned_addr);

        uint32_t value;
        switch (addr & 0x3)
        {
        case 0:
            value = (aligned_load & 0xFFFFFF00) | (gpr[rt] >> 24);
            break;
        case 1:
            value = (aligned_load & 0xFFFF0000) | (gpr[rt] >> 16);
            break;
        case 2:
            value = (aligned_load & 0xFF000000) | (gpr[rt] >> 8);
            break;
        case 3:
            value = gpr[rt]; break;
        }

        write<uint32_t>(aligned_addr, value);
    }

    void IOProcessor::op_lwr()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t rs = instr.i_type.rs;
        int16_t imm = (int16_t)instr.i_type.immediate;

        uint32_t addr = gpr[rs] + imm;
        uint32_t aligned_addr = addr & 0xFFFFFFFC;
        uint32_t aligned_load = read<uint32_t>(aligned_addr);

        uint32_t value = 0;
        uint32_t LRValue = gpr[rt];

        if (rt == memory_load.reg)
        {
            LRValue = memory_load.value;
        }

        switch (addr & 0x3)
        {
        case 0:
            value = aligned_load;
            break;
        case 1:
            value = (LRValue & 0xFF000000) | (aligned_load >> 8);
            break;
        case 2:
            value = (LRValue & 0xFFFF0000) | (aligned_load >> 16);
            break;
        case 3:
            value = (LRValue & 0xFFFFFF00) | (aligned_load >> 24);
            break;
        }

        load(rt, value);
    }

    void IOProcessor::op_lwl()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t rs = instr.i_type.rs;
        int16_t imm = (int16_t)instr.i_type.immediate;

        uint32_t addr = gpr[rs] + imm;
        uint32_t aligned_addr = addr & 0xFFFFFFFC;
        uint32_t aligned_load = read<uint32_t>(aligned_addr);

        uint32_t value = 0;
        uint32_t LRValue = gpr[rt];

        if (rt == memory_load.reg)
        {
            LRValue = memory_load.value;
        }

        switch (addr & 0x3)
        {
        case 0:
            value = (LRValue & 0x00FFFFFF) | (aligned_load << 24);
            break;
        case 1:
            value = (LRValue & 0x0000FFFF) | (aligned_load << 16);
            break;
        case 2:
            value = (LRValue & 0x000000FF) | (aligned_load << 8);
            break;
        case 3:
            value = aligned_load;
            break;
        }

        load(rt, value);
    }

    void IOProcessor::op_xori()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t rs = instr.i_type.rs;
        uint32_t imm = instr.i_type.immediate;

        set_reg(rt, gpr[rs] ^ imm);

        log("XORI: GPR[{:d}] = GPR[{:d}] ({:#x}) ^ {:#x}\n", rt, rs, gpr[rs], imm);
    }

    void IOProcessor::op_sub()
    {
        uint16_t rd = instr.r_type.rd;
        uint16_t rt = instr.r_type.rt;
        uint16_t rs = instr.r_type.rs;

        uint32_t sub = gpr[rs] - gpr[rt];
        set_reg(rd, sub);

        log("SUB: GPR[{:d}] = GPR[{:d}] ({:#x}) - GPR[{:d}] ({:#x})\n", rd, rs, gpr[rs], rt, gpr[rt]);
    }

    void IOProcessor::op_mult()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t rs = instr.i_type.rs;

        int64_t result = (int64_t)(int32_t)gpr[rs] * (int64_t)(int32_t)gpr[rt];

        hi = result >> 32;
        lo = result;

        log("MULT: GPR[{:d}] ({:#x}) * GPR[{:d}] ({:#x}) = {:#x} STORED IN LO = {:#x} and HI = {:#x}\n", rs, gpr[rs], rt, gpr[rt], result, lo, hi);
    }

    void IOProcessor::op_break()
    {
        exception(Exception::Break);

        log("BREAK\n");
    }

    void IOProcessor::op_xor()
    {
        uint16_t rd = instr.r_type.rd;
        uint16_t rt = instr.r_type.rt;
        uint16_t rs = instr.r_type.rs;

        set_reg(rd, gpr[rs] ^ gpr[rt]);

        log("XOR: GPR[{:d}] = GPR[{:d}] ({:#x}) ^ GPR[{:d}] ({:#x})\n", rd, rs, gpr[rs], rt, gpr[rt]);
    }

    void IOProcessor::op_multu()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t rs = instr.i_type.rs;

        uint64_t result = (uint64_t)gpr[rs] * (uint64_t)gpr[rt];

        hi = result >> 32;
        lo = result;

        log("MULTU: GPR[{:d}] ({:#x}) * GPR[{:d}] ({:#x}) = {:#x} STORED IN LO = {:#x} and HI = {:#x}\n", rs, gpr[rs], rt, gpr[rt], result, lo, hi);
    }

    void IOProcessor::op_srlv()
    {
        uint16_t rd = instr.r_type.rd;
        uint16_t rt = instr.r_type.rt;
        uint16_t rs = instr.r_type.rs;

        uint16_t sa = gpr[rs] & 0x1F;
        set_reg(rd, gpr[rt] >> sa);

        log("SRLV: GPR[{:d}] = GPR[{:d}] ({:#x}) >> {:d}\n", rd, rt, gpr[rt], sa);
    }

    void IOProcessor::op_srav()
    {
        uint16_t rd = instr.r_type.rd;
        uint16_t rt = instr.r_type.rt;
        uint16_t rs = instr.r_type.rs;

        uint16_t sa = gpr[rs] & 0x1F;
        int32_t reg = (int32_t)gpr[rt];
        set_reg(rd, reg >> sa);

        log("SRAV: GPR[{:d}] = GPR[{:d}] ({:#x}) >> {:d}\n", rd, rt, reg, sa);
    }

    void IOProcessor::op_nor()
    {
        uint16_t rd = instr.r_type.rd;
        uint16_t rt = instr.r_type.rt;
        uint16_t rs = instr.r_type.rs;

        uint32_t result = ~(gpr[rs] | gpr[rt]);
        set_reg(rd, result);

        log("NOR: GPR[{:d}] = NOT {:#x} = GPR[{:d}] ({:#x}) | GPR[{:d}] ({:#x})\n", rd, result, rs, gpr[rs], rt, gpr[rt]);
    }

    void IOProcessor::op_lh()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t base = instr.i_type.rs;
        int16_t offset = (int16_t)instr.i_type.immediate;

        uint32_t vaddr = gpr[base] + offset;
        if (!cop0.sr.IsC)
        {
Geo Ster's avatar
Geo Ster committed
            if (vaddr & 0x1) [[unlikely]]
Geo Ster's avatar
Geo Ster committed
            {
                cop0.BadA = vaddr;
                exception(Exception::ReadError);
            }
            else
            {
                uint32_t value = (int16_t)read<uint16_t>(vaddr);
                load(rt, value);
            }
        }

        log("LH: GPR[{:d}] = {:#x} from address {:#x} = GPR[{:d}] ({:#x}) + {:#x}\n", rt, gpr[rt], vaddr, base, gpr[base], offset);
    }

    void IOProcessor::op_sllv()
    {
        uint16_t rd = instr.r_type.rd;
        uint16_t rt = instr.r_type.rt;
        uint16_t rs = instr.r_type.rs;

        uint16_t sa = gpr[rs] & 0x1F;
        set_reg(rd, gpr[rt] << sa);

        log("SLLV: GPR[{:d}] = GPR[{:d}] ({:#x}) << GPR[{:d}] ({:d})\n", rd, rt, gpr[rt], rs, sa);
    }

    void IOProcessor::op_lhu()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t base = instr.i_type.rs;
        int16_t offset = (int16_t)instr.i_type.immediate;

        uint32_t vaddr = gpr[base] + offset;
        if (!cop0.sr.IsC) 
        {
Geo Ster's avatar
Geo Ster committed
            if (vaddr & 0x1) [[unlikely]]
Geo Ster's avatar
Geo Ster committed
            {
                cop0.BadA = vaddr;
                exception(Exception::ReadError, 0);
            }
            else
            {
                uint32_t value = read<uint16_t>(vaddr);
                load(rt, value);
            }
        }

        log("LHU: GPR[{:d}] = {:#x} from address {:#x} = GPR[{:d}] ({:#x}) + {:#x}\n", rt, gpr[rt], vaddr, base, gpr[base], offset);
    }

    void IOProcessor::op_rfe()
    {
        uint32_t mode = cop0.sr.value & 0x3F;

        /* Shift kernel/user mode bits back. */
        cop0.sr.value &= ~(uint32_t)0xF;
        cop0.sr.value |= mode >> 2;

        log("RFE\n");
    }

    void IOProcessor::op_mthi()
    {
        uint16_t rs = instr.r_type.rs;
        hi = gpr[rs];

        log("MTHI: HI = GPR[{:d}] ({:#x})\n", rs, gpr[rs]);
    }

    void IOProcessor::op_mtlo()
    {
        uint16_t rs = instr.r_type.rs;
        lo = gpr[rs];

        log("MTLO: LO = GPR[{:d}] ({:#x})\n", rs, gpr[rs]);
    }

    void IOProcessor::op_syscall()
    {
        exception(Exception::Syscall, 0);

        log("SYSCALL\n");
    }

    void IOProcessor::op_slt()
    {
        uint16_t rd = instr.r_type.rd;
        uint16_t rt = instr.r_type.rt;
        uint16_t rs = instr.r_type.rs;

        int32_t reg1 = (int32_t)gpr[rs];
        int32_t reg2 = (int32_t)gpr[rt];
        bool condition = reg1 < reg2;
        set_reg(rd, condition);

        log("SLT: GPR[{:d}] = GPR[{:d}] ({:#x}) < GPR[{:d}] ({:#x})\n", rd, rs, reg1, rt, reg2);
    }

    void IOProcessor::op_mfhi()
    {
        uint16_t rd = instr.r_type.rd;
        set_reg(rd, hi);

        log("MFHI: GPR[{:d}] = HI ({:#x})\n", rd, hi);
    }

    void IOProcessor::op_divu()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t rs = instr.i_type.rs;

        uint32_t dividend = gpr[rs];
        uint32_t divisor = gpr[rt];
Geo Ster's avatar
Geo Ster committed
        if (divisor == 0) [[unlikely]]
Geo Ster's avatar
Geo Ster committed
        {
            hi = dividend;
            lo = 0xFFFFFFFF;
        }
        else 
        {
            hi = dividend % divisor;
            lo = dividend / divisor;
        }

        log("DIVU: GPR[{:d}] ({:#x}) / GPR[{:d}] ({:#x}) OUTPUT LO0 = {:#x} and HI0 = {:#x}\n", rs, gpr[rs], rt, gpr[rt], lo, hi);
    }

    void IOProcessor::op_sltiu()
    {
        uint16_t rt = instr.r_type.rt;
        uint16_t rs = instr.r_type.rs;
        uint32_t imm = instr.i_type.immediate;

        bool condition = gpr[rs] < imm;
        set_reg(rt, condition);

        log("SLTIU: GPR[{:d}] = GPR[{:d}] ({:#x}) < {:#x}\n", rt, rs, gpr[rs], imm);
    }

    void IOProcessor::op_srl()
    {
        uint16_t rd = instr.r_type.rd;
        uint16_t rt = instr.r_type.rt;
        uint16_t sa = instr.r_type.sa;

        set_reg(rd, gpr[rt] >> sa);

        log("SRL: GPR[{:d}] = GPR[{:d}] ({:#x}) >> {:d}\n", rd, rt, gpr[rt], sa);
    }

    void IOProcessor::op_mflo()
    {
        uint16_t rd = instr.r_type.rd;
        set_reg(rd, lo);

        log("MFLO: GPR[{:d}] = LO ({:#x})\n", rd, lo);
    }

    void IOProcessor::op_div()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t rs = instr.i_type.rs;

        int32_t dividend = (int32_t)gpr[rs];
        int32_t divisor = (int32_t)gpr[rt];
Geo Ster's avatar
Geo Ster committed
        if (divisor == 0) [[unlikely]]
Geo Ster's avatar
Geo Ster committed
        {
            hi = gpr[rs];
            lo = (dividend >= 0 ? 0xFFFFFFFF : 1);
        }
Geo Ster's avatar
Geo Ster committed
        else if (gpr[rs] == 0x80000000 && divisor == -1) [[unlikely]]
Geo Ster's avatar
Geo Ster committed
        {
            hi = 0;
            lo = 0x80000000;
        }
        else 
        {
            hi = (uint32_t)(dividend % divisor);
            lo = (uint32_t)(dividend / divisor);
        }

        log("DIV: LO = GPR[{:d}] ({:#x}) / GPR[{:d}] ({:#x})\n", rs, dividend, rt, divisor);
    }

    void IOProcessor::op_sra()
    {
        uint16_t rd = instr.r_type.rd;
        uint16_t rt = instr.r_type.rt;
        uint16_t sa = instr.r_type.sa;

        int32_t reg = (int32_t)gpr[rt];
        set_reg(rd, reg >> sa);

        log("SRA: GPR[{:d}] = GPR[{:d}] ({:#x}) >> {:d}\n", rd, rt, reg, sa);
    }

    void IOProcessor::op_subu()
    {
        uint16_t rd = instr.r_type.rd;
        uint16_t rt = instr.r_type.rt;
        uint16_t rs = instr.r_type.rs;

        set_reg(rd, gpr[rs] - gpr[rt]);

        log("SUBU: GPR[{:d}] = GPR[{:d}] ({:#x}) - GPR[{:d}] ({:#x})\n", rd, rs, gpr[rs], rt, gpr[rt]);
    }

    void IOProcessor::op_slti()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t rs = instr.i_type.rs;
        int16_t imm = (int16_t)instr.i_type.immediate;

        int32_t reg = (int32_t)gpr[rs];
        bool condition = reg < imm;
        set_reg(rt, condition);

        log("SLTI: GPR[{:d}] = GPR[{:d}] ({:#x}) < {:#x}\n", rt, rs, reg, imm);
    }

    void IOProcessor::branch()
    {
        int32_t imm = (int16_t)instr.i_type.immediate;

        next_instr.branch_taken = true;
        pc = next_instr.pc + (imm << 2);
    }

    void IOProcessor::op_jalr()
    {
        uint16_t rd = instr.r_type.rd;

        set_reg(rd, instr.pc + 8);
        op_jr();
    }

    void IOProcessor::op_lbu()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t base = instr.i_type.rs;
        int16_t offset = (int16_t)instr.i_type.immediate;

        uint32_t vaddr = gpr[base] + offset;
        if (!cop0.sr.IsC) 
        {
            uint32_t value = read<uint8_t>(vaddr);
            load(rt, value);
        }

        log("LBU: GPR[{:d}] = {:#x} from address {:#x} = GPR[{:d}] ({:#x}) + {:#x}\n", rt, gpr[rt], vaddr, base, gpr[base], offset);
    }

    void IOProcessor::op_blez()
    {
        uint16_t rs = instr.i_type.rs;

        next_instr.is_delay_slot = true;
        int32_t reg = (int32_t)gpr[rs];
        if (reg <= 0)
        {
            branch();
        }

        log("BLEZ: IF GPR[{:d}] ({:#x}) <= 0 THEN PC = {:#x}\n", rs, gpr[rs], pc);
    }

    void IOProcessor::op_bgtz()
    {
        uint16_t rs = instr.i_type.rs;

        next_instr.is_delay_slot = true;
        int32_t reg = (int32_t)gpr[rs];
        if (reg > 0)
        {
            branch();
        }

        log("BGTZ: IF GPR[{:d}] ({:#x}) > 0 THEN PC = {:#x}\n", rs, gpr[rs], pc);
    }

    void IOProcessor::op_add()
    {
        uint16_t rd = instr.r_type.rd;
        uint16_t rt = instr.r_type.rt;
        uint16_t rs = instr.r_type.rs;

        uint32_t add = gpr[rs] + gpr[rt];
        set_reg(rd, add);

        log("ADD: GPR[{:d}] = GPR[{:d}] ({:#x}) + GPR[{:d}] ({:#x})\n", rd, rs, gpr[rs], rt, gpr[rt]);
    }

    void IOProcessor::op_and()
    {
        uint16_t rd = instr.r_type.rd;
        uint16_t rt = instr.r_type.rt;
        uint16_t rs = instr.r_type.rs;

        set_reg(rd, gpr[rs] & gpr[rt]);

        log("AND: GPR[{:d}] = GPR[{:d}] ({:#x}) & GPR[{:d}] ({:#x})\n", rd, rs, gpr[rs], rt, gpr[rt]);
    }

    void IOProcessor::op_mfc0()
    {
        uint16_t rd = instr.r_type.rd;
        uint16_t rt = instr.r_type.rt;

        load(rt, cop0.regs[rd]);

        log("MFC0: GPR[{:d}] = COP0_REG[{:d}] ({:#x})\n", rt, rd, cop0.regs[rd]);
    }

    void IOProcessor::op_beq()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t rs = instr.i_type.rs;

        next_instr.is_delay_slot = true;
        if (gpr[rs] == gpr[rt]) 
        {
            branch();
        }

        log("BEQ: IF GPR[{:d}] ({:#x}) == GPR[{:d}] ({:#x}) THEN PC = {:#x}\n", rt, gpr[rt], rs, gpr[rs], pc);
    }

    void IOProcessor::op_lb()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t base = instr.i_type.rs;
        int16_t offset = (int16_t)instr.i_type.immediate;

        uint32_t vaddr = gpr[base] + offset;
        if (!cop0.sr.IsC) 
        {
            uint32_t value = (int8_t)read<uint8_t>(vaddr);
            load(rt, value);
        }

        log("LB: GPR[{:d}] = {:#x} from address {:#x} = GPR[{:d}] ({:#x}) + {:#x}\n", rt, gpr[rt], vaddr, base, gpr[base], offset);

    }

    void IOProcessor::op_jr()
    {
        uint16_t rs = instr.i_type.rs;

        pc = gpr[rs];
        next_instr.is_delay_slot = true;
        next_instr.branch_taken = true;

        log("JR: Jumped to GPR[{:d}] = {:#x}\n", rs, pc);
    }

    void IOProcessor::op_sb()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t base = instr.i_type.rs;
        int16_t offset = (int16_t)instr.i_type.immediate;

        uint32_t vaddr = gpr[base] + offset;
        if (!cop0.sr.IsC)
        {
            write<uint8_t>(vaddr, gpr[rt]);
        }

        log("SB: Writing GPR[{:d}] ({:#x}) to address {:#x} = GPR[{:d}] ({:#x}) + {:d}\n", rt, gpr[rt], vaddr, base, gpr[base], offset);
    }

    void IOProcessor::op_andi()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t rs = instr.i_type.rs;
        uint16_t imm = instr.i_type.immediate;

        set_reg(rt, gpr[rs] & imm);

        log("ANDI: GPR[{:d}] = GPR[{:d}] ({:#x}) & {:#x}\n", rt, rs, gpr[rs], imm);
    }

    void IOProcessor::op_jal()
    {
        set_reg(31, pc);
        op_j();
    }

    void IOProcessor::op_sh()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t base = instr.i_type.rs;
        int16_t offset = (int16_t)instr.i_type.immediate;

        uint32_t vaddr = gpr[base] + offset;
        if (!cop0.sr.IsC) 
        {
Geo Ster's avatar
Geo Ster committed
            if (vaddr & 0x1) [[unlikely]]
Geo Ster's avatar
Geo Ster committed
            {
                cop0.BadA = vaddr;
                exception(Exception::WriteError, 0);
            }
            else 
            {
                write<uint16_t>(vaddr, gpr[rt]);
            }
        }

        log("SH: Writing GPR[{:d}] ({:#x}) to address {:#x} = GPR[{:d}] ({:#x}) + {:d}\n", rt, gpr[rt], vaddr, base, gpr[base], offset);
    }

    void IOProcessor::op_addu()
    {
        uint16_t rt = instr.r_type.rt;
        uint16_t rs = instr.r_type.rs;
        uint16_t rd = instr.r_type.rd;

        set_reg(rd, gpr[rs] + gpr[rt]);

        log("ADDU: GPR[{:d}] = GPR[{:d}] ({:#x}) + GPR[{:d}] ({:#x})\n", rd, rs, gpr[rs], rt, gpr[rt]);
    }

    void IOProcessor::op_sltu()
    {
        uint16_t rt = instr.r_type.rt;
        uint16_t rs = instr.r_type.rs;
        uint16_t rd = instr.r_type.rd;

        bool condition = gpr[rs] < gpr[rt];
        set_reg(rd, condition);

        log("SLTU: GPR[{:d}] = GPR[{:d}] ({:#x}) < GPR[{:d}] ({:#x})\n", rd, rs, gpr[rs], rt, gpr[rt]);
    }

    void IOProcessor::op_lw()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t base = instr.i_type.rs;
        int16_t offset = (int16_t)instr.i_type.immediate;

        uint32_t vaddr = gpr[base] + offset;
        if (!cop0.sr.IsC) 
        {
Geo Ster's avatar
Geo Ster committed
            if (vaddr & 0x3) [[unlikely]]
Geo Ster's avatar
Geo Ster committed
            {
                cop0.BadA = vaddr;
                exception(Exception::ReadError, 0);
            }
            else 
            {
                uint32_t value = read<uint32_t>(vaddr);
                load(rt, value);
            }
        }

        log("LW: GPR[{:d}] = {:#x} from address {:#x} = GPR[{:d}] ({:#x}) + {:#x}\n", rt, gpr[rt], vaddr, base, gpr[base], offset);
    }

    void IOProcessor::op_addi()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t rs = instr.i_type.rs;
        int16_t imm = (int16_t)instr.i_type.immediate;

        int32_t reg = (int32_t)gpr[rs];
        set_reg(rt, reg + imm);

        log("ADDI: GPR[{:d}] = GPR[{:d}] ({:#x}) + {:#x}\n", rt, rs, reg, imm);
    }

    void IOProcessor::op_bne()
    {
        uint16_t rt = instr.i_type.rt;
        uint16_t rs = instr.i_type.rs;

        next_instr.is_delay_slot = true;
        if (gpr[rs] != gpr[rt])
        {
            branch();
        }

        log("BNE: IF GPR[{:d}] ({:#x}) != GPR[{:d}] ({:#x}) THEN PC = {:#x}\n", rt, gpr[rt], rs, gpr[rs], pc);
    }

    void IOProcessor::op_mtc0()
    {
        uint16_t rt = instr.r_type.rt;
        uint16_t rs = instr.r_type.rs;
        uint16_t rd = instr.r_type.rd;

        cop0.regs[rd] = gpr[rt];

        log("MTC0: COP0[{:d}] = GPR[{:d}] ({:#x})\n", rd, rt, gpr[rt]);
    }

    void IOProcessor::op_or()
    {
        uint16_t rt = instr.r_type.rt;
        uint16_t rs = instr.r_type.rs;
        uint16_t rd = instr.r_type.rd;

        set_reg(rd, gpr[rs] | gpr[rt]);

        log("OR: GPR[{:d}] = GPR[{:d}] ({:#x}) | GPR[{:d}] ({:#x})\n", rd, rs, gpr[rs], rt, gpr[rt]);
    }

    void IOProcessor::op_j()
    {
        pc = (pc & 0xF0000000) | (instr.j_type.target << 2);
        next_instr.is_delay_slot = true;
        next_instr.branch_taken = true;

        log("J: Jumping to PC = {:#x}\n", pc);
    }

    void IOProcessor::op_addiu()