<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbMultLdStHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbMultLdStHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xF000) == 0xC000);
    // uint8_t opcode = (instruction & 0x0800) >> 11;
    constexpr bool opcode = (op & 0x020);
    uint8_t rList = instruction & 0x00FF;
    //uint8_t rb = (instruction & 0x0700) >> 8;
    constexpr uint8_t rb = (op & 0x01C) >> 2;
    uint32_t rbValue = cpu->getRegister(rb);
    uint32_t oldRbValue = rbValue;

    bool firstAccess = true;

    // In THUMB mode stack is always meant to be 'full descending', 
    // ie. PUSH is equivalent to 'STMFD/STMDB' and POP to 'LDMFD/LDMIA' in ARM mode.

    if constexpr(!opcode) {
        // 0: STMIA Rb!,{Rlist}   ;store in memory, increments Rb (post-increment store)
        for(int i = 0; i < 8; i++) {
            if(rList & 0x01) {
                if(firstAccess) {
                    cpu->bus->write32(rbValue, cpu->getRegister(i), Bus::CycleType::NONSEQUENTIAL);
                    firstAccess = false;
                } else {
                    cpu->bus->write32(rbValue, cpu->getRegister(i), Bus::CycleType::SEQUENTIAL);
                }
                rbValue += 4;
            }
            rList >>= 1;
        }
        if(!(instruction & 0x00FF)) {
            // empty rList
            // R15 loaded/stored (ARMv4 only), and Rb=Rb+40h (ARMv4-v5). 
            cpu->bus->write32(rbValue, cpu->getRegister(PC_REGISTER) + 4, Bus::CycleType::NONSEQUENTIAL);
            rbValue += 0x40;
        }
        if(((uint32_t)rList >> rb) & 0x1) {
            // if rb included in rList, Store OLD base if Rb is FIRST entry in Rlist, 
            // otherwise store NEW base (STM/ARMv4)
            if(!(rList << (8 - rb))) {
                // rb is first entry in rList
                cpu->setRegister(rb, oldRbValue);   
            } else {
                cpu->setRegister(rb, rbValue);
            }
        } else {
            cpu->setRegister(rb, rbValue);
        }    
    } else {
        // 1: LDMIA Rb!,{Rlist}   ;load from memory, increments Rb (post-increment load)
        for(int i = 0; i < 8; i++) {
            if(rList & 0x01) {
                if(firstAccess) {
                    cpu->setRegister(i, aluShiftRor(cpu->bus->read32(rbValue, Bus::CycleType::NONSEQUENTIAL), 
                                                    (rbValue & 3) * 8));
                    firstAccess = false;
                } else {
                    cpu->setRegister(i, aluShiftRor(cpu->bus->read32(rbValue, Bus::CycleType::SEQUENTIAL), 
                                                    (rbValue & 3) * 8));
                }
                rbValue += 4;
            }
            rList >>= 1;
        }
        if(!(instruction & 0x00FF)) {
            // empty rList
            // R15 loaded/stored (ARMv4 only), and Rb=Rb+40h (ARMv4-v5).
            cpu->setRegister(PC_REGISTER, aluShiftRor(cpu->bus->read32(rbValue, Bus::CycleType::NONSEQUENTIAL), 
                                                     (rbValue & 3) * 8));
            rbValue += 0x40;
        }
        if(((uint32_t)rList >> rb) & 0x1) {
            // if rb included in rList, no write back for stm
        } else {
            cpu->setRegister(rb, rbValue);
        }        
    }

    cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
    return NONSEQUENTIAL;
}

<|start_filename|>src/memory/EEPROM.cpp<|end_filename|>
#include "EEPROM.h"
#include "../util/macros.h"

#include "assert.h"


// thanks to https://densinh.github.io/DenSinH/emulation/2021/02/01/gba-eeprom.html
// for the explanation of EEPROM 

void EEPROM::transferBitToEeprom(bool bit) {
    if(currTransferBit == 0) {

        readyToRead = false;
        writeComplete = false;
        firstBit = bit;
    } else if(currTransferBit == 1) {

        currAddressBit = busWidth - 1;
        address = 0;
        op = (uint8_t)firstBit | ((uint8_t)bit << 1);
        if(op == READ_OP) {
            // read
            currTransferSize = readSize;
        } else if(op == WRITE_OP) {    
            // write
            currWriteValueBit = 63;
            valueToWrite = 0;
            currTransferSize = writeSize;
        } else {
            DEBUGWARN((uint32_t)op << " :invalid eeprom op\n");
        }
    } else if(currTransferBit < (currTransferSize - 1)) {

        if(op == READ_OP) {
            // read 
            address |= (((uint32_t)bit) << currAddressBit);
            currAddressBit -= 1;
        } else {
            // write
            if(currAddressBit >= 0) {
                address |= (((uint32_t)bit) << currAddressBit);
                currAddressBit -= 1;
            } else if(currWriteValueBit >= 0){
                valueToWrite |= (((uint64_t)bit) << currWriteValueBit);
                currWriteValueBit -= 1;
            }
        }
    } else {

        // transfer over
        currTransferBit = -1;
        if(op == READ_OP) {
            // read
            valueToRead = eeprom[address & 0x3FF];
            readyToRead = true;
        } else {
            // write
            eeprom[address & 0x3FF] = valueToWrite;
            writeComplete = true;
        }
        op = 0;
    }
    currTransferBit++;
}

uint32_t EEPROM::receiveBitFromEeprom() {
    uint32_t returnBit = 0;
    if(writeComplete) {

        // TODO: it'll take ca. 108368 clock cycles (ca. 6.5ms) 
        // until the old data is erased and new data is programmed.
        currReceivingBit = 0;
        returnBit = 1;
    } else if(!readyToRead) {

        currReceivingBit = 0;
        returnBit = 1;
    } else {

        // ready to read
        if(currReceivingBit < 4) {
            // do nothing
            currReceivingBit++;
            returnBit = 0;
        } else if(currReceivingBit < 68) {
            currReceivingBit++;
            returnBit = (valueToRead >> currReadValueBit) & 0x1;;
            currReadValueBit -= 1;
        } else {
            currReadValueBit = 63;
            currReceivingBit = 1;
        }
    }
    return returnBit;
}

void EEPROM::setBusWidth(uint32_t width) {
    assert(width == 6 || width == 14);

    if(width == 6) {
        busWidth = 6;
        writeSize = SIX_BIT_WRITE_SIZE;
        readSize = SIX_BIT_READ_SIZE;
    } else {    
        // wdith == 14
        busWidth = 14;
        writeSize = FOURTEEN_BIT_WRITE_SIZE;
        readSize = FOURTEEN_BIT_READ_SIZE;
    }
}

<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbSwiHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbSwiHandler(uint16_t instruction, ARM7TDMI* cpu) {
    // 11011111b: SWI nn   ;software interrupt
    // 10111110b: BKPT nn  ;software breakpoint (ARMv5 and up) not used in ARMv4T
    assert((instruction & 0xFF00) == 0xDF00);

    // cpu->cpsr=<changed>  ;Enter svc/abt
    cpu->switchToMode(Mode::SUPERVISOR);
    *(cpu->currentSpsr) = cpu->cpsr;
    cpu->cpsr.Mode = Mode::SUPERVISOR;
    // ARM state, IRQs disabled
    cpu->cpsr.T = 0;
    cpu->cpsr.I = 1;

    // R14_svc=PC+2    ;save return address
    // TODO explain why you arent adding 2
    cpu->setRegister(14, cpu->getRegister(PC_REGISTER));

    // PC=VVVV0008h    ;jump to SWI/PrefetchAbort vector address
    // TODO: is base always 0000?
    // TODO: make vector addresses static members
    cpu->setRegister(PC_REGISTER, 0x00000008);

    return BRANCH; 
}


<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbUndefHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"


template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbUndefHandler(uint16_t instruction, ARM7TDMI* cpu) {
    DEBUGWARN("UNDEFINED THUMB OPCODE! " << std::bitset<16>(instruction).to_string() << std::endl);
    
    // cpu->switchToMode(ARM7TDMI::Mode::UNDEFINED);
    // TODO: what is behaviour of thumb undefined instruction?
    cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
    return BRANCH;
}


<|start_filename|>src/Gamepad.h<|end_filename|>
#include <SFML/Graphics.hpp>

class Bus;

class Gamepad {

    public:
        static void getInput(Bus* bus);

        // TODO make configurable
        static const sf::Keyboard::Key A = sf::Keyboard::K;
        static const sf::Keyboard::Key B = sf::Keyboard::M;
        static const sf::Keyboard::Key DPAD_LEFT = sf::Keyboard::A;
        static const sf::Keyboard::Key DPAD_UP = sf::Keyboard::W;
        static const sf::Keyboard::Key DPAD_RIGHT = sf::Keyboard::D;
        static const sf::Keyboard::Key DPAD_DOWN = sf::Keyboard::S;
        static const sf::Keyboard::Key SHOULDER_RIGHT = sf::Keyboard::E;
        static const sf::Keyboard::Key SHOULDER_LEFT = sf::Keyboard::Q;
        static const sf::Keyboard::Key START = sf::Keyboard::Space;
        static const sf::Keyboard::Key SELECT = sf::Keyboard::RShift;

};

<|start_filename|>src/arm7tdmi/ARMInstructions/ArmBranchHandler.h<|end_filename|>


#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::armBranchHandler(uint32_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0x0E000000) == 0x0A000000);

    int32_t offset = ((instruction & 0x00FFFFFF) << 2);
    if(offset & 0x02000000) {
        // negative? Then must sign extend
        offset |= 0xFE000000;
    }

    BitPreservedInt32 pcVal;
    pcVal._unsigned = cpu->getRegister(PC_REGISTER);
    BitPreservedInt32 branchAddr;

    // TODO might be able to remove +4 when after implekemting pipelining
    branchAddr._signed = pcVal._signed + offset + 4;
    branchAddr._unsigned &= 0xFFFFFFFC;

    if constexpr(!(op & 0x100)) {
        // B
    } else {
        // BL LR=PC+4
        cpu->setRegister(LINK_REGISTER, cpu->getRegister(PC_REGISTER));
    }

    cpu->setRegister(PC_REGISTER, branchAddr._unsigned);
    return BRANCH;
}

<|start_filename|>src/GameBoyAdvanceImpl.cpp<|end_filename|>
#include "GameBoyAdvanceImpl.h"

#include <fstream>
#include <iostream>
#include <unistd.h>
#include <iterator>
#include <chrono>
#include <algorithm> 

#include "arm7tdmi/ARM7TDMI.h"
#include "memory/Bus.h"
#include "LCD.h"
#include "PPU.h"
#include "Gamepad.h"
#include "DMA.h"
#include "Timer.h"
#include "Debugger.h"

using milliseconds = std::chrono::milliseconds;

uint64_t GameBoyAdvanceImpl::cyclesSinceStart = 0;

GameBoyAdvanceImpl::GameBoyAdvanceImpl() {
    this->arm7tdmi = std::make_shared<ARM7TDMI>();
    this->bus =  std::make_shared<Bus>();
    this->screen =  std::make_shared<LCD>();
    arm7tdmi->connectBus(bus);
    this->ppu =  std::make_shared<PPU>();
    ppu->connectBus(bus);
    this->bus->connectPpu(ppu);
    this->dma =  std::make_shared<DMA>();
    bus->connectDma(dma);
    dma->connectBus(bus);
    dma->connectCpu(arm7tdmi);
    this->timer = std::make_shared<Timer>();
    this->timer->connectBus(bus);
    bus->connectTimer(timer);
    this->timer->connectCpu(arm7tdmi);
    this->scheduler =  std::make_shared<Scheduler>();
    dma->connectScheduler(scheduler);
    timer->connectScheduler(scheduler);
    this->debugger =  std::make_shared<Debugger>();
}

void GameBoyAdvanceImpl::printCpuState() {\
    Debugger::stepMode = true;
    debugger->step(arm7tdmi.get(), bus.get());
    debugger->printState();
}

// if rom loading successful return true, else return false
bool GameBoyAdvanceImpl::loadRom(std::string path) { 
    std::ifstream binFile(path, std::ios::binary);
    if(binFile.fail()) {
        std::cerr << "could not find file" << std::endl;
        return false;
    }
    std::vector<uint8_t> buffer(std::istreambuf_iterator<char>(binFile), {});
    
    bus->loadRom(buffer); 
    arm7tdmi->initializeWithRom();
    return true;
}

void GameBoyAdvanceImpl::testDisplay() {
    screen->initWindow();
}

long getCurrentTime() {
    return  std::chrono::duration_cast<milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}

uint64_t GameBoyAdvanceImpl::getTotalCyclesElapsed() {
    return totalCycles;
}

void GameBoyAdvanceImpl::enterMainLoop() {
    screen->initWindow();
    // add initial events
    scheduler->addEvent(Scheduler::EventType::HBLANK, PPU::H_VISIBLE_CYCLES, Scheduler::EventCondition::NULL_CONDITION, false);
    scheduler->addEvent(Scheduler::EventType::VBLANK, PPU::V_VISIBLE_CYCLES, Scheduler::EventCondition::NULL_CONDITION, false);
    scheduler->addEvent(Scheduler::EventType::HBLANK_END, 0, Scheduler::EventCondition::NULL_CONDITION, false);
    scheduler->addEvent(Scheduler::EventType::VBLANK_END, 227 * PPU::H_TOTAL, Scheduler::EventCondition::NULL_CONDITION, false);
    bus->iORegisters[Bus::IORegister::DISPSTAT] &= (~0x1);
    bus->iORegisters[Bus::IORegister::DISPSTAT] &= (~0x2);

    uint16_t currentScanline = -1;
    uint16_t nextScanline = 0;
    uint64_t cyclesSinceLastScanline = 0;
    previousTime = getCurrentTime();
    double previous60Frame = getCurrentTime();
    startTimeSeconds = getCurrentTime() / 1000.0;

    // TODO: initialize this somewhere else
    bus->iORegisters[Bus::IORegister::KEYINPUT] = 0xFF;
    bus->iORegisters[Bus::IORegister::KEYINPUT + 1] = 0x03;
    double fps = 60.0;

    // STARTING MAIN EMULATION LOOP!
    while(true) {
        if(debugMode) {
            debugger->step(arm7tdmi.get(), bus.get());
            if(Debugger::stepMode) {
                while(sf::Keyboard::isKeyPressed(sf::Keyboard::LShift) && debugMode && Debugger::stepMode) {

                };
                while(!sf::Keyboard::isKeyPressed(sf::Keyboard::LShift) && debugMode && Debugger::stepMode) {
                    if(sf::Keyboard::isKeyPressed(sf::Keyboard::X)) {
                        std::cout << "Leaving DEBUG mode!\n";
                        debugMode = false;
                        Debugger::stepMode = false;
                        break;
                    }
                };
                debugger->printState();
            }
        }

       if(!bus->haltMode) {
            uint32_t cpuCycles = arm7tdmi->step();
            cyclesSinceStart += cpuCycles;
        } else {
            if(((bus->iORegisters[Bus::IORegister::IE] & bus->iORegisters[Bus::IORegister::IF]) || 
               ((bus->iORegisters[Bus::IORegister::IE + 1] & 0x3F) & (bus->iORegisters[Bus::IORegister::IF + 1] & 0x3F)))) {
                // halt mode over, interrupt fired
                bus->haltMode = false;
            } else {
                // skip to next event
                cyclesSinceStart = scheduler->peekNextEvent()->startCycle;
            }
        }

        Scheduler::Event* nextEvent = scheduler->getNextEvent(cyclesSinceStart);
        
        while(nextEvent != nullptr) {
            uint64_t eventCycles = 0;
            switch(nextEvent->eventType) {
                case Scheduler::EventType::DMA0: {
                    dmaXEvent(0, nextEvent, currentScanline);
                    break;
                }
                case Scheduler::EventType::DMA1: {
                    dmaXEvent(1, nextEvent, currentScanline);             
                    break;
                }
                case Scheduler::EventType::DMA2: {
                    dmaXEvent(2, nextEvent, currentScanline);                 
                    break;
                }
                case Scheduler::EventType::DMA3: {
                    dmaXEvent(3, nextEvent, currentScanline);
                    break;
                }
                case Scheduler::EventType::TIMER0: {
                    timer->timerXOverflowEvent(0);
                    break;
                }
                case Scheduler::EventType::TIMER1: {
                    timer->timerXOverflowEvent(1);
                    break;
                }
                case Scheduler::EventType::TIMER2: {
                    timer->timerXOverflowEvent(2);
                    break;
                }
                case Scheduler::EventType::TIMER3: {
                    timer->timerXOverflowEvent(3);
                    break;
                }
                case Scheduler::EventType::VBLANK: {
                    // vblank time!
                    // (do frame stuff)
                    // TODO: put some of this stuff to separate methods / classes
                    if(bus->iORegisters[Bus::IORegister::DISPSTAT] & 0x8) {
                        arm7tdmi->queueInterrupt(ARM7TDMI::Interrupt::VBlank);
                    }
                    Gamepad::getInput(bus.get());

                    // setting vblank flag to 1
                    bus->iORegisters[Bus::IORegister::DISPSTAT] |= 0x1;

                    frames++;
                    
                    while(getCurrentTime() - previousTime < 17) {
                        usleep(500);
                    }

                    if((frames % 60) == 0) {
                        double smoothing = 0.8;
                        fps = fps * smoothing + ((double)60 / ((getCurrentTime() / 1000.0 - previous60Frame / 1000.0))) * (1.0 - smoothing);
                        std::cout << "fps: " << fps << "\n";
                        previous60Frame = previousTime;
                    }

                    previousTime = getCurrentTime();
                    screen->drawWindow(ppu->renderCurrentScreen());  

                    if(sf::Keyboard::isKeyPressed(sf::Keyboard::Z)) {
                        std::cout << "Entering DEBUG mode! Press LSHIFT to step through CPU instructions\n";
                        debugMode = true;
                        Debugger::stepMode = true;
                    }
                    // add next vblank event
                    scheduler->addEvent(Scheduler::EventType::VBLANK, 
                                        PPU::V_TOTAL - ((cyclesSinceStart + PPU::V_VISIBLE_CYCLES) % PPU::V_TOTAL), 
                                        Scheduler::EventCondition::NULL_CONDITION, 
                                        false);                    
                    break;
                }
                case Scheduler::EventType::HBLANK: {      
                    // hblank time!
                    if(bus->iORegisters[Bus::IORegister::DISPSTAT] & 0x10) {
                        arm7tdmi->queueInterrupt(ARM7TDMI::Interrupt::HBlank);
                    }
                    bus->iORegisters[Bus::IORegister::DISPSTAT] |= 0x2;

                    // add next hblank event
                    scheduler->addEvent(Scheduler::EventType::HBLANK,
                                        PPU::H_TOTAL - ((cyclesSinceStart + PPU::H_VISIBLE_CYCLES) % PPU::H_TOTAL), 
                                        Scheduler::EventCondition::NULL_CONDITION,
                                        false);
                    
                    break;
                }
                case Scheduler::EventType::VBLANK_END: {
                    bus->iORegisters[Bus::IORegister::DISPSTAT] &= (~0x1);
                    // add next vblank end event
                    scheduler->addEvent(Scheduler::EventType::VBLANK_END, 
                                        PPU::V_TOTAL - (cyclesSinceStart % PPU::V_TOTAL), 
                                        Scheduler::EventCondition::NULL_CONDITION, 
                                        false);
                    break;
                }
                case Scheduler::EventType::HBLANK_END: {
                    ppu->renderScanline(currentScanline);
                    // setting hblank flag to 0
                    currentScanline += (cyclesSinceStart - cyclesSinceLastScanline) / PPU::H_TOTAL;    
                    cyclesSinceLastScanline = cyclesSinceStart - (cyclesSinceStart % PPU::H_TOTAL);
                    currentScanline %= 228;
                    nextScanline = (currentScanline + 1) % 228;

                    
                    
                    bus->iORegisters[Bus::IORegister::DISPSTAT] &= (~0x2);
                    if(currentScanline == ((uint16_t)(bus->iORegisters[Bus::IORegister::DISPSTAT + 1]))) {
                        // current scanline == vcount bits in DISPSTAT
                        // set vcounter flag
                        bus->iORegisters[Bus::IORegister::DISPSTAT] |= 0x04;
                        if(bus->iORegisters[Bus::IORegister::DISPSTAT] & 0x20) {
                            // if vcount irq enabled, queue the interrupt!
                            arm7tdmi->queueInterrupt(ARM7TDMI::Interrupt::VCounterMatch);
                        }
                    } else {
                        // toggle vcounter flag off
                        bus->iORegisters[Bus::IORegister::DISPSTAT] &= (~0x04);

                    }

                    bus->iORegisters[Bus::IORegister::VCOUNT] = currentScanline;

                    // add next hblank end event
                    scheduler->addEvent(Scheduler::EventType::HBLANK_END, 
                                        PPU::H_TOTAL - (cyclesSinceStart % PPU::H_TOTAL), 
                                        Scheduler::EventCondition::NULL_CONDITION, 
                                        false);
                    break;
                }
                default: {
                    break;
                    //assert(false);
                }
            }
            nextEvent = scheduler->getNextEvent(cyclesSinceStart);
        }
        
    }
}


ARM7TDMI* GameBoyAdvanceImpl::getCpu() {
    return arm7tdmi.get();
}


inline 
void GameBoyAdvanceImpl::dmaXEvent(uint8_t x, Scheduler::Event* dmaEvent, uint16_t currentScanline) {
    if(dmaEvent->eventCondition == Scheduler::EventCondition::NULL_CONDITION) {
        dma->dmaX(x, false, false, currentScanline);
    } else if(dmaEvent->eventCondition == Scheduler::EventCondition::HBLANK_START) {
        dma->dmaX(x, false, true, currentScanline);
    } else if(dmaEvent->eventCondition == Scheduler::EventCondition::VBLANK_START) {
        dma->dmaX(x, true, false, currentScanline);
    } else {
        // EventCondition == DMA3Videomode;
        dma->dmaX(x, false, true, currentScanline);
    }       
}





<|start_filename|>src/arm7tdmi/ARMInstructions/ArmMultHandler.h<|end_filename|>

#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

template<uint16_t op> 
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::armMultHandler(uint32_t instruction, ARM7TDMI* cpu) {

    constexpr uint8_t opcode = (op & 0x1E0) >> 5;
    constexpr bool s = (op & 0x010);
    // rd is different for multiply
    uint8_t rd = (instruction & 0x000F0000) >> 16;
    uint8_t rm = getRm(instruction);
    uint8_t rs = getRs(instruction);
    assert(rd != rm && (rd != PC_REGISTER && rm != PC_REGISTER && rs != PC_REGISTER));
    assert((instruction & 0x000000F0) == 0x00000090);
    uint64_t result;
    BitPreservedInt64 longResult;
    int internalCycles;

    if constexpr(opcode == 0b0000) {// MUL
        uint32_t rsVal = cpu->getRegister(rs);
        result = (uint64_t)cpu->getRegister(rm) * (uint64_t)rsVal;
        cpu->setRegister(rd, (uint32_t)result);
        internalCycles = mulGetExecutionTimeMVal(rsVal);
    } else if constexpr(opcode == 0b0001) {  // MLA
        // rn is different for multiply
        uint8_t rn = (instruction & 0x0000F000) >> 12;
        uint32_t rsVal = cpu->getRegister(rs);
        assert(rn != PC_REGISTER);
        result = (uint64_t)cpu->getRegister(rm) *
                    (uint64_t)rsVal +
                    (uint64_t)cpu->getRegister(rn);
        cpu->setRegister(rd, (uint32_t)result);
        internalCycles = mulGetExecutionTimeMVal(rsVal) + 1;
    } else if constexpr(opcode ==  0b0100) {  // UMULL{cond}{S} RdLo,RdHi,Rm,Rs ;RdHiLo=Rm*Rs
        uint32_t rsVal = cpu->getRegister(rs);
        uint8_t rdhi = rd;
        uint8_t rdlo = (instruction & 0x0000F000) >> 12;
        longResult._unsigned = (uint64_t)cpu->getRegister(rm) * (uint64_t)rsVal;
        // high destination reg
        cpu->setRegister(rdhi, (uint32_t)(longResult._unsigned >> 32));
        cpu->setRegister(rdlo, (uint32_t)longResult._unsigned);
        // TODO: this is possible quite inefficient
        internalCycles = umullGetExecutionTimeMVal(rsVal) + 1;
    } else if constexpr(opcode == 0b0101) {  // UMLAL {cond}{S} RdLo,RdHi,Rm,Rs ;RdHiLo=Rm*Rs+RdHiLo
        uint32_t rsVal = cpu->getRegister(rs);
        uint8_t rdhi = rd;
        uint8_t rdlo = (instruction & 0x0000F000) >> 12;
        longResult._unsigned =
            (uint64_t)cpu->getRegister(rm) *
            (uint64_t)cpu->getRegister(rs) +
            ((((uint64_t)(cpu->getRegister(rdhi))) << 32) | ((uint64_t)(cpu->getRegister(rdlo))));
        // high destination reg
        cpu->setRegister(rdhi, (uint32_t)(longResult._unsigned >> 32));
        cpu->setRegister(rdlo, (uint32_t)longResult._unsigned);
        internalCycles = umullGetExecutionTimeMVal(rsVal) + 2;
    } else if constexpr(opcode == 0b0110) {  // SMULL {cond}{S} RdLo,RdHi,Rm,Rs ;RdHiLo=Rm*Rs
        uint32_t rsValRaw = cpu->getRegister(rs);
        uint8_t rdhi = rd;
        uint8_t rdlo = (instruction & 0x0000F000) >> 12;
        BitPreservedInt32 rmVal;
        BitPreservedInt32 rsVal;
        rmVal._unsigned = cpu->getRegister(rm);
        rsVal._unsigned = rsValRaw;
        longResult._signed =
            (int64_t)rmVal._signed * (int64_t)rsVal._signed;
        // high destination reg
        cpu->setRegister(rdhi, (uint32_t)(longResult._unsigned >> 32));
        cpu->setRegister(rdlo, (uint32_t)longResult._unsigned);
        internalCycles = mulGetExecutionTimeMVal(rsValRaw) + 1;
    } else if constexpr(opcode == 0b0111) {  // SMLAL{cond}{S} RdLo,RdHi,Rm,Rs ;RdHiLo=Rm*Rs+RdHiLo
        uint32_t rsValRaw = cpu->getRegister(rs);
        uint8_t rdhi = rd;
        uint8_t rdlo = (instruction & 0x0000F000) >> 12;
        BitPreservedInt32 rmVal;
        BitPreservedInt32 rsVal;
        rmVal._unsigned = cpu->getRegister(rm);
        rsVal._unsigned = rsValRaw;
        BitPreservedInt64 accum;
        accum._unsigned = ((((uint64_t)(cpu->getRegister(rdhi))) << 32) |
                            ((uint64_t)(cpu->getRegister(rdlo))));
        longResult._signed = (int64_t)rmVal._signed * (int64_t)rsVal._signed + accum._signed;
        // high destination reg
        cpu->setRegister(rdhi, (uint32_t)(longResult._unsigned >> 32));
        cpu->setRegister(rdlo, (uint32_t)longResult._unsigned);
        internalCycles = mulGetExecutionTimeMVal(rsValRaw) + 2;
    }
    
  if constexpr(s) {
        if constexpr(!(opcode & 0b0100)) {  // regular mult opcode,
            cpu->cpsr.Z = aluSetsZeroBit((uint32_t)result);
            cpu->cpsr.N = aluSetsSignBit((uint32_t)result);
        } else {
            cpu->cpsr.Z = (longResult._unsigned == 0);
            cpu->cpsr.N = (longResult._unsigned >> 63);
        }
        // cpsr.C = 0;
        // cpsr.V = 0;
    }
    // TODO: can probably optimize this greatly
    for(int i = 0; i < internalCycles; i++) {
        cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
    }
    if(internalCycles == 0) {
        return SEQUENTIAL;
    } else {
        return NONSEQUENTIAL;
    }
}

<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbBxHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

// op = instruction & 0xFFC0;
// anything within the mask can be constexpr!

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbBxHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xFC00) == 0x4400);
    //uint8_t opcode = (instruction & 0x0300) >> 8;
    constexpr uint8_t opcode = (op & 0x00C) >> 2;
    uint8_t rs = thumbGetRs(instruction);
    uint8_t rd = thumbGetRd(instruction);
    //uint8_t msbd = (instruction & 0x0080) >> 7;  // MSBd - Destination Register most significant bit (or BL/BLX flag)
    //uint8_t msbs = (instruction & 0x0040) >> 6;  // MSBs - Source Register most significant bit
    constexpr bool msbd = op & 0x002;
    constexpr bool msbs = op & 0x001;

    if constexpr(msbs) {
        rs = rs | 0x8;
    } 
    if constexpr(msbd) {
        rd = rd | 0x8;
    } 

    uint32_t rsVal = cpu->getRegister(rs);
    uint32_t rdVal = cpu->getRegister(rd);
    rsVal = (rs == PC_REGISTER) ? (rsVal + 2) & 0xFFFFFFFE : rsVal;
    rdVal = (rd == PC_REGISTER) ? (rdVal + 2) & 0xFFFFFFFE : rdVal;

    // TODO: why do we have to do this even if rs != 15? 
    if(rd == PC_REGISTER) {
        rsVal &= 0xFFFFFFFE;
    }

    if constexpr(opcode == 0) {
        // 0: ADD Rd,Rs   ;add        Rd = Rd+Rs
        assert(msbd || msbs);
        uint32_t result = rdVal + rsVal;
        cpu->setRegister(rd, result);
        if(rd == PC_REGISTER) {
            return BRANCH;
        } else {
            return SEQUENTIAL;
        }
    } else if constexpr(opcode == 1) {
        // 1: CMP Rd,Rs   ;compare  Void = Rd-Rs  ;cpu->cpsr affected
        uint32_t result;
        result = rdVal - rsVal;
        cpu->cpsr.N = aluSetsSignBit((uint32_t)result);
        cpu->cpsr.Z = aluSetsZeroBit((uint32_t)result);
        cpu->cpsr.C = aluSubtractSetsCarryBit(rdVal, rsVal);
        cpu->cpsr.V = aluSubtractSetsOverflowBit(rdVal, rsVal, result);
        return BRANCH;
    } else if constexpr(opcode == 2) {
        // 2: MOV Rd,Rs   ;move       Rd = Rs
        cpu->setRegister(rd, rsVal);
        assert(msbd || msbs);
        if(rd == PC_REGISTER) {
            return BRANCH;
        } else {
            return SEQUENTIAL;
        }    
    } else {
        // opcode == 3
        // 3: BX  Rs      ;jump       PC = Rs     ;may switch THUMB/ARM
        if (rs == PC_REGISTER || !(rsVal & 0x1)) {
            // R15: CPU switches to ARM state, and PC is auto-aligned as
            // (($+4) AND NOT 2).
            // For BX/BLX, when Bit 0 of the value in Rs is zero:
            // Processor will be switched into ARM mode!
            // If so, Bit 1 of Rs must be cleared (32bit word aligned).
            // Thus, BX PC (switch to ARM) may be issued from word-aligned
            // address only, the destination is PC+4 (ie. the following
            // halfword is skipped).
            cpu->cpsr.T = 0;
            rsVal &= 0xFFFFFFFD;
        }
        assert(msbd == 0);
        rsVal &= 0xFFFFFFFE;
        cpu->setRegister(PC_REGISTER, rsVal);

        return BRANCH; 
    }

}


<|start_filename|>src/memory/Bus.cpp<|end_filename|>
#include "Bus.h"
#include "../PPU.h"
#include "BIOS.h"
#include "../Timer.h"
#include "../DMA.h"
#include "../arm7tdmi/ARM7TDMI.h"
#include "../util/macros.h"

#include "assert.h"

#include <fstream>
#include <iostream>
#include <iterator>
#include <regex>



Bus::Bus() {
    // TODO: make bios configurable
    for(int i = 0; i < 98688; i++) {
        vRam.push_back(0);
    }
    BIOS biosData;
    for(int i = 0; i < biosData.size; i++) {
        bios.push_back(biosData.data[i]);
    }
    for(int i = 0; i < 263168; i++) {
        wRamBoard.push_back(0);
    }
    for(int i = 0; i < 32896; i++) {
        wRamChip.push_back(0);
    }
    for(int i = 0; i < 1028; i++) {
        iORegisters.push_back(0);
    }
    // Initialized to 0D000020h (by hardware). Unlike all other I/O registers, 
    // this register is mirrored across the whole I/O area (in increments of 64K, 
    // ie. at 4000800h, 4010800h, 4020800h, ..., 4FF0800h)
    // TODO:
    // iORegisters[INTERNAL_MEM_CNT] = 0x20;
    // iORegisters[INTERNAL_MEM_CNT + 1] = 0x00;
    // iORegisters[INTERNAL_MEM_CNT + 2] = 0x00;
    // iORegisters[INTERNAL_MEM_CNT + 3] = 0x0D;

    for(int i = 0; i < 1028; i++) {
        paletteRam.push_back(0);
    }
    for(int i = 0; i < 1028; i++) {
        objAttributes.push_back(0);
    }
    for(int i = 0; i < 69000; i++) {
        gamePakSram.push_back(0);
    }
    // TODO, does gamepak have to be initialized with 32MB of memory?
    gamePakRom.resize(32000000);
}

Bus::~Bus() {
}

inline
uint32_t readFromArray32(std::vector<uint8_t>* arr, uint32_t address, uint32_t shift) {
        return (uint32_t)arr->at(address - shift) |
               (uint32_t)arr->at((address + 1) - shift) << 8 |
               (uint32_t)arr->at((address + 2) - shift) << 16 |
               (uint32_t)arr->at((address + 3) - shift) << 24;
}

inline
uint16_t readFromArray16(std::vector<uint8_t>* arr, uint32_t address, uint32_t shift) {
        return (uint16_t)arr->at(address - shift) |
               (uint16_t)arr->at((address + 1) - shift) << 8;
}

inline
uint8_t readFromArray8(std::vector<uint8_t>* arr, uint32_t address, uint32_t shift) {
        return (uint8_t)arr->at(address - shift);
}

inline
void writeToArray32(std::vector<uint8_t>* arr, uint32_t address, uint32_t shift, uint32_t value) {
        arr->at(address - shift) = (uint8_t)value;
        arr->at((address + 1) - shift) = (uint8_t)(value >> 8);
        arr->at((address + 2) - shift) = (uint8_t)(value >> 16);
        arr->at((address + 3) - shift) = (uint8_t)(value >> 24);
}

inline
void writeToArray16(std::vector<uint8_t>* arr, uint32_t address, uint32_t shift, uint16_t value) {
        arr->at(address - shift) = (uint8_t)value;
        arr->at((address + 1) - shift) = (uint8_t)(value >> 8);
}

inline
void writeToArray8(std::vector<uint8_t>* arr, uint32_t address, uint32_t shift, uint8_t value) {
        arr->at(address - shift) = (uint8_t)value;
}

void Bus::addCycleToExecutionTimeline(CycleType cycleType, uint32_t shift, uint8_t width) {
    if(cycleType == CycleType::INTERNAL) {
        executionTimelineCycles[executionTimelineSize] = 1;
        executionTimelineCycleType[executionTimelineSize] = cycleType;
        executionTimelineSize++; 
        return;                        
    }

    switch(shift) {
        case 0: // bios
        case 0x03000000:  // CHIP RAM    
        case 0x04000000: { // IO
            executionTimelineCycles[executionTimelineSize] = 1;
            executionTimelineCycleType[executionTimelineSize] = cycleType;
            executionTimelineSize++;            
            break;
        }       
        case 0x02000000: { // BOARD RAM
            switch(width) {
                case 32: {
                    executionTimelineCycles[executionTimelineSize] = 6;
                    executionTimelineCycleType[executionTimelineSize] = cycleType;
                    executionTimelineSize++;
                    break;
                }
                case 16:
                case 8: {
                    executionTimelineCycles[executionTimelineSize] = 3;
                    executionTimelineCycleType[executionTimelineSize] = cycleType;
                    executionTimelineSize++;
                    break;
                }
            }
            break;
        }       
        case 0x05000000:  // palette RAM       
        case 0x06000000: { // VRAM
            switch(width) {
                case 32: {
                    executionTimelineCycles[executionTimelineSize] = 2;
                    executionTimelineCycleType[executionTimelineSize] = cycleType;
                    executionTimelineSize++;
                    break;
                }
                case 16:
                case 8: {
                    executionTimelineCycles[executionTimelineSize] = 1;
                    executionTimelineCycleType[executionTimelineSize] = cycleType;
                    executionTimelineSize++;
                    break;
                }
            }
            break;

        }       
        case 0x07000000: { // OBJ Attributes
            executionTimelineCycles[executionTimelineSize] = 1;
            executionTimelineCycleType[executionTimelineSize] = cycleType;
            executionTimelineSize++;            
            break;
        }       
        case 0x08000000: { // gamepak waitstate 0
            switch(width) {
                case 32: {
                    if(cycleType == SEQUENTIAL) {
                        executionTimelineCycles[executionTimelineSize] = getWaitState0SCycles() + 1 + 
                                                                        getWaitState0SCycles() + 1;
                    } else {
                        executionTimelineCycles[executionTimelineSize] = getWaitState0NCycles() + 1 + 
                                                                        getWaitState0SCycles() + 1;
                    }
                    executionTimelineCycleType[executionTimelineSize] = cycleType;
                    executionTimelineSize++;
                    break;
                }
                case 16:
                case 8: {
                    if(cycleType == SEQUENTIAL) {
                        executionTimelineCycles[executionTimelineSize] = getWaitState0SCycles() + 1;
                    } else {
                        executionTimelineCycles[executionTimelineSize] = getWaitState0NCycles() + 1;
                    }
                    executionTimelineCycleType[executionTimelineSize] = cycleType;
                    executionTimelineSize++;
                    break;
                }
            }  
            break; 
        } 
        case 0x0A000000: { // gamepak waitstate 1
            switch(width) {
                case 32: {
                    if(cycleType == SEQUENTIAL) {
                        executionTimelineCycles[executionTimelineSize] = getWaitState1SCycles() + 1 + 
                                                                        getWaitState1SCycles() + 1;
                    } else {
                        executionTimelineCycles[executionTimelineSize] = getWaitState1NCycles() + 1 + 
                                                                        getWaitState1SCycles() + 1;
                    }
                    executionTimelineCycleType[executionTimelineSize] = cycleType;
                    executionTimelineSize++;
                    break;
                }
                case 16:
                case 8: {
                    if(cycleType == SEQUENTIAL) {
                        executionTimelineCycles[executionTimelineSize] = getWaitState1SCycles() + 1;
                    } else {
                        executionTimelineCycles[executionTimelineSize] = getWaitState1NCycles() + 1;
                    }
                    executionTimelineCycleType[executionTimelineSize] = cycleType;
                    executionTimelineSize++;
                    break;
                }
            }   
            break;
        }  
        case 0x0C000000: { // gamepak waitstate 2
            switch(width) {
                case 32: {
                    if(cycleType == SEQUENTIAL) {
                        executionTimelineCycles[executionTimelineSize] = getWaitState2SCycles() + 1 + 
                                                                        getWaitState2SCycles() + 1;
                    } else {
                        executionTimelineCycles[executionTimelineSize] = getWaitState2NCycles() + 1 + 
                                                                        getWaitState2SCycles() + 1;
                    }
                    executionTimelineCycleType[executionTimelineSize] = cycleType;
                    executionTimelineSize++;
                    break;
                }
                case 16:
                case 8: {
                    if(cycleType == SEQUENTIAL) {
                        executionTimelineCycles[executionTimelineSize] = getWaitState2SCycles() + 1;
                    } else {
                        executionTimelineCycles[executionTimelineSize] = getWaitState2NCycles() + 1;
                    }
                    executionTimelineCycleType[executionTimelineSize] = cycleType;
                    executionTimelineSize++;
                    break;
                }
            }   
            break;        
        } 
        case 0x0E000000: {
            if(width == 8) {
                executionTimelineCycles[executionTimelineSize] = 5;
                executionTimelineCycleType[executionTimelineSize] = cycleType;
                executionTimelineSize++;                  
            }
            break;
        } 
    }
}

inline
uint32_t Bus::read(uint32_t address, uint8_t width, CycleType cycleType) {
    // TODO: use same switch statement pattern as in fn addCycleToExecutionTimeline
    memAccessCycles += 1;
    uint32_t shift = (address & 0xFF000000) >> 24;
    //addCycleToExecutionTimeline(cycleType, address & 0x0F000000, width);

    /*
        TODO: The BIOS memory is protected against reading, 
        the GBA allows to read opcodes or data only if the program counter 
        is located inside of the BIOS area. If the program counter is not 
        in the BIOS area, reading will return the most recent successfully 
        fetched BIOS opcode (eg. the opcode at [00DCh+8] after startup and SoftReset, 
        the opcode at [0134h+8] during IRQ execution, and opcode at [013Ch+8] after 
        IRQ execution, and opcode at [0188h+8] after SWI execution).
        (see mgba tests)
    */

    switch(shift) {
        case 0x0:
        case 0x01: {
            
            if(0x00004000 <= address && address <= 0x01FFFFFF)  {
                break;
            }
            switch(width) {
                case 32: {
                    return readFromArray32(&bios, align32(address), 0);
                }
                case 16: {
                    return readFromArray16(&bios, align16(address), 0);
                }
                case 8: {
                    return readFromArray8(&bios, address, 0);
                }
                default: {
                    assert(false);
                    break;
                }
                break;
            }
            break;
        }
        case 0x02: { // board RAM 
            address &= 0x0203FFFF;
            switch(width) {
                case 32: {
                    memAccessCycles += 5;
                    return readFromArray32(&wRamBoard, align32(address), 0x02000000);
                }
                case 16: {
                    memAccessCycles += 2;
                    return readFromArray16(&wRamBoard, align16(address), 0x02000000);            
                }
                case 8: {
                    memAccessCycles += 2;
                    return readFromArray8(&wRamBoard, address, 0x02000000);            
                }
                default: {
                    assert(false);
                    break;
                }
            }
            break;
        }   
        case 0x03: {   
            // mirrored every 8000 bytes
            address &= 0x03007FFF;
            if(address > 0x03007FFF) {
                break;
            }
            switch(width) {
                case 32: {
                    return readFromArray32(&wRamChip, align32(address), 0x03000000);
                }
                case 16: {
                    return readFromArray16(&wRamChip, align16(address), 0x03000000);            
                }
                case 8: {
                    return readFromArray8(&wRamChip, address, 0x03000000);            
                }
                default: {
                    assert(false);
                    break;
                }
            }     
            break;
        }      
        case 0x04: {
            if(address > 0x040003FE) {
                // TODO: handle strange io mem accesses
                break;
            }
            uint32_t upperLimit = address + (width / 8);
            if(0x4000102 <= upperLimit && address <= 0x400010F) {
                // timer addresses
                timer->updateBusToPrepareForTimerRead(address, width);
            }

            switch(width) {
                case 32: {
                    return readFromArray32(&iORegisters, align32(address), 0x04000000);
                }
                case 16: {
                    return readFromArray16(&iORegisters, align16(address), 0x04000000);            
                }
                case 8: {
                    return readFromArray8(&iORegisters, address, 0x04000000);            
                }
                default: {
                    assert(false);
                    break;
                }
            }    
            break;       
        }     
        case 0x05: {  
            // if(address > 0x050003FF) {
            //     break;
            // }  
            address &= 0x050003FF;
            switch(width) {
                case 32: {
                    return readFromArray32(&paletteRam, align32(address), 0x05000000);
                }
                case 16: {
                    return readFromArray16(&paletteRam, align16(address), 0x05000000);            
                }
                case 8: {
                    return readFromArray8(&paletteRam, address, 0x05000000);            
                }
                default: {
                    assert(false);
                    break;
                }
            } 
            break;
        }    
        case 0x06: {    
            // Even though VRAM is sized 96K (64K+32K), it is repeated in steps of 128K 
            // (64K+32K+32K, the two 32K blocks itself being mirrors of each other).
            //address &= 0x06017FFF;
            if(address & 0x00010000) {
                address &= 0x06007FFF;
                address += 0x10000;
            } else {
                address &= 0x0600FFFF;
            }
            switch(width) {
                case 32: {
                    return readFromArray32(&vRam, align32(address), 0x06000000);
                }
                case 16: {
                    return readFromArray16(&vRam, align16(address), 0x06000000);            
                }
                case 8: {
                    return readFromArray8(&vRam, address, 0x06000000);            
                }
                default: {
                    assert(false);
                    break;
                }
            }    
            break;
        }        
        case 0x07: {   
            address &= 0x070003FF;
            switch(width) {
                case 32: {
                    return readFromArray32(&objAttributes, align32(address), 0x07000000);
                }
                case 16: {
                    return readFromArray16(&objAttributes, align16(address), 0x07000000);            
                }
                case 8: {
                    return readFromArray8(&objAttributes, address, 0x07000000);           
                }
                default: {
                    assert(false);
                    break;
                }
            }
            break;
        }      
        case 0x08:
        case 0x09: {
            //  TODO: *** Separate timings for sequential, and non-sequential accesses.
            // waitstate 0 
            address &= 0x00FFFFFF;

            
            if(unlikely(address >= 0x00FFFF00 && cartSaveType == EEPROM_TYPE)) {
                return eeprom.receiveBitFromEeprom();
            }

            switch(width) {
                case 32: {
                    memAccessCycles += 7;
                    return readFromArray32(&gamePakRom, align32(address), 0x00000000);
                }
                case 16: {
                    memAccessCycles += 4;
                    return readFromArray16(&gamePakRom, align16(address), 0x00000000);            
                }
                case 8: {  
                    memAccessCycles += 4; 
                    return readFromArray8(&gamePakRom, address, 0x00000000);           
                }
                default: {
                    assert(false);
                    break;
                }
            } 
            break;  
        } 
        case 0x0A:
        case 0x0B: {
            //  TODO: *** Separate timings for sequential, and non-sequential accesses.
            // waitstate 1
            address &= 0x00FFFFFF;

            if(unlikely(address >= 0x00FFFF00 && cartSaveType == EEPROM_TYPE)) {
                return eeprom.receiveBitFromEeprom();
            }

            switch(width) {
                case 32: {
                    return readFromArray32(&gamePakRom, align32(address), 0x00000000);
                }
                case 16: {
                    return readFromArray16(&gamePakRom, align16(address), 0x00000000);            
                }
                case 8: {
                    return readFromArray8(&gamePakRom, address, 0x00000000);            
                }
                default: {
                    assert(false);
                    break;
                }
            }   
            break;
        } 
        case 0x0C:
        case 0x0D:  {
            //  TODO: *** Separate timings for sequential, and non-sequential accesses.
            // waitstate 2
            address &= 0x00FFFFFF;

            if(!largeCart) {
                if(unlikely(shift == 0x0D && cartSaveType == EEPROM_TYPE)) {
                    return eeprom.receiveBitFromEeprom();
                }
            }

            if(unlikely(address >= 0x00FFFF00 && cartSaveType == EEPROM_TYPE)) {
                return eeprom.receiveBitFromEeprom();
            }

            switch(width) {
                case 32: {
                    return readFromArray32(&gamePakRom, align32(address), 0x00000000);
                }
                case 16: {
                    return readFromArray16(&gamePakRom, align16(address), 0x00000000);           
                }
                case 8: {
                    return readFromArray8(&gamePakRom, address, 0x00000000);            
                }
                default: {
                    assert(false);
                    break;
                }
            } 
            break;  
        }
        case 0x0E:
        case 0x0F:  {
            // The 64K SRAM area is mirrored across the whole 32MB area at E000000h-FFFFFFFh, 
            // also, inside of the 64K SRAM field, 32K SRAM chips are repeated twice.

            if(cartSaveType == FLASH512_TYPE || cartSaveType == FLASH1024_TYPE) {
                if(0x0E000000 <= address && address <= 0x0E00FFFF) {
                    return flash.read(address);
                } 
            }    

            address &= 0x00007FFF;

            switch(width) {
                case 32: {
                    return (((uint32_t)readFromArray8(&gamePakSram, address, 0x00000000)) * 0x01010101);      
                }
                case 16: {
                    return ((uint32_t)readFromArray8(&gamePakSram, address, 0x00000000)) * 0x0101;    
                }
                case 8: {
                    return readFromArray8(&gamePakSram, address, 0x00000000);           
                }
                default: {
                    assert(false);
                    break;
                }
            } 

            break;
        }
        default: {  
            // TODO: implement unused memory access behaviour (and check for unused memory writes)
            break;
        }

    }

    return 436207618U;
}

inline
void Bus::write(uint32_t address, uint32_t value, uint8_t width, CycleType accessType) {
    // TODO: use same switch statement pattern as in fn addCycleToExecutionTimeline
    //addCycleToExecutionTimeline(accessType, address & 0x0F000000, width);
    // TODO: templates for specifying width
    memAccessCycles += 1;
    uint32_t shift = (address & 0xFF000000) >> 24;

    switch(shift) {
        case 0x0:
        case 0x01: {    
            break;   
        }
        case 0x02: { // BOARD RAM  
            address &= 0x0203FFFF;

            switch(width) {
                case 32: {
                    memAccessCycles += 5;
                    writeToArray32(&wRamBoard, align32(address), 0x02000000, value);
                    break;
                }
                case 16: {
                    memAccessCycles += 2;
                    writeToArray16(&wRamBoard, align16(address), 0x02000000, value);    
                    break;        
                }
                case 8: {
                    memAccessCycles += 2;
                    writeToArray8(&wRamBoard, address, 0x02000000, value);    
                    break;       
                }
                default: {
                    assert(false);
                    break;
                }
            }
            break; 
        }
        case 0x03: {
            // mirrored every 8000 bytes
            address &= 0x03007FFF;
            switch(width) {
                case 32: {
                    writeToArray32(&wRamChip, align32(address), 0x03000000, value); 
                    break;
                }
                case 16: {
                    writeToArray16(&wRamChip, align16(address), 0x03000000, value);       
                    break;     
                }
                case 8: {
                    writeToArray8(&wRamChip, address, 0x03000000, value);             
                    break;
                }
                default: {
                    assert(false);
                    break;
                }
            } 
            break;        
        }
        case 0x04: {
            if(address > 0x040003FE) {
                // TODO handle strange io memory access
                break;
            }
            uint32_t upperLimit = address + (width / 8);
            if(0x4000102 <= upperLimit && address <= 0x400010F) {
                // timer addresses
                timer->updateTimerUponWrite(address, value, width);
            }


            // TODO: there's a more efficient way to do this I think,
            // send the changed register to DMA AFTER the write happens
            if(0x40000BA <= upperLimit && address <= 0x40000DF) {
                // dma addresses
                dma->updateDmaUponWrite(address, value, width);
            }

            switch(width) {
                case 32: {
                    writeToArray32(&iORegisters, align32(address), 0x04000000, value); 
                    break;
                }
                case 16: {
                    writeToArray16(&iORegisters, align16(address), 0x04000000, value);      
                    break;      
                }
                case 8: {
                    writeToArray8(&iORegisters, address, 0x04000000, value);         
                    break;  
                }
                default: {
                    assert(false);
                    break;
                }
            } 

            // SPECIAL CASE when writing to interrupt request register
            // setting a bit (acknowledging an interrupt) changes that bit to zero
            // so do val &= ~val
            if(0x4000202 <= upperLimit && address <= 0x4000203) {

                uint8_t tempWidth = width;
                uint32_t tempAddress = address;
                uint32_t tempValue = value;
                while(tempWidth != 0) {
                    
                    if(tempAddress == 0x04000202 || tempAddress == 0x04000203) {
                        iORegisters[tempAddress - 0x04000000] = iORegisters[tempAddress - 0x04000000] & (~tempValue);
                    }
                    
                    tempWidth -= 8;
                    tempAddress += 1;
                    tempValue = tempValue >> 8;
                }
            }   

            if(address == 0x04000301) {
                // halt register hit
                if(!(iORegisters[HALTCNT] & 0x80)) {
                    haltMode = true;
                }
            }           
            break;
        }
        case 0x05: {  
            address &= 0x050003FF;
            switch(width) {
                case 32: {
                    writeToArray32(&paletteRam, align32(address), 0x05000000, value); 
                    break;
                }
                case 16: {
                    writeToArray16(&paletteRam, align16(address), 0x05000000, value);         
                    break;    
                }
                case 8: {
                    /*
                    Writes to BG (6000000h-600FFFFh) (or 6000000h-6013FFFh in Bitmap mode) 
                    and to Palette (5000000h-50003FFh) are writing the new 8bit value to
                    BOTH upper and lower 8bits of the addressed halfword, ie. "[addr AND NOT 1]=data*101h".
                    */
                    writeToArray16(&paletteRam, align16(address), 0x05000000, value * 0x101); 
                    break;            
                }
                default: {
                    assert(false);
                    break;
                }
            } 
            break;
        } 
        case 0x06: {  

            // Even though VRAM is sized 96K (64K+32K), it is repeated in steps of 128K 
            // (64K+32K+32K, the two 32K blocks itself being mirrors of each other).
                if(address & 0x00010000) {
                    address &= 0x06007FFF;
                    address += 0x10000;
                } else {
                    address &= 0x0600FFFF;
                }
            //address = (address & 0x0607FFF);
            switch(width) {
                case 32: {
                    writeToArray32(&vRam, align32(address), 0x06000000, value);
                    break;
                }
                case 16: {
                    writeToArray16(&vRam, align16(address), 0x06000000, value);    
                    break;        
                }
                case 8: {
                    writeToArray8(&vRam, address, 0x06000000, value);      
                    break;      
                }
                default: {
                    assert(false);
                    break;
                }
            }
        
            break;
        } 
        case 0x07: {   
            // TODO: there are more hblank rules to implement
            address &= 0x070003FF;
            switch(width) {
                case 32: {
                    writeToArray32(&objAttributes, align32(address), 0x07000000, value);
                    break;
                }
                case 16: {
                    writeToArray16(&objAttributes, align16(address), 0x07000000, value);          
                    break;  
                }
                case 8: {
                    // 8 bit Writes to OBJ (6010000h-6017FFFh) (or 6014000h-6017FFFh in Bitmap mode) 
                    // and to OAM (7000000h-70003FFh) are ignored, the memory content remains unchanged.
                    // writeToArray8(&objAttributes, address, 0x07000000, value);            
                    break;
                }
                default: {
                    assert(false);
                    break;
                }
            }
            break;   

        }
        case 0x08:
        case 0x09: {
            //  TODO: *** Separate timings for sequential, and non-sequential accesses.
            // waitstate 0

            if((address & 0x00FFFFFF) >= 0x00FFFF00 && cartSaveType == EEPROM_TYPE) {
                eeprom.transferBitToEeprom(value & 0x1);
            }

            switch(width) {
                case 32: {
                    //writeToArray32(&gamePakRom, address, 0x08000000, value);
                    break;
                }
                case 16: {
                    //writeToArray16(&gamePakRom, address, 0x08000000, value);         
                    break;   
                }
                case 8: {
                    //writeToArray8(&gamePakRom, address, 0x08000000, value);          
                    break;  
                }
                default: {
                    assert(false);
                    break;
                }
            }   

            break;
        }
        case 0x0A:
        case 0x0B: { 
            //  TODO: *** Separate timings for sequential, and non-sequential accesses.
            // waitstate 1
            //assert(false);

            if((address & 0x00FFFFFF) >= 0x00FFFF00 && cartSaveType == EEPROM_TYPE) {
                eeprom.transferBitToEeprom(value & 0x1);
            }

            switch(width) {
                case 32: {
                    //writeToArray32(&gamePakRom, address, 0x0A000000, value);
                    break;
                }
                case 16: {
                    //writeToArray16(&gamePakRom, address, 0x0A000000, value);         
                    break;   
                }
                case 8: {
                    //writeToArray8(&gamePakRom, address, 0x0A000000, value);           
                    break;
                }
                default: {
                    assert(false);
                    break;
                }
            }   
            break;
        }
        case 0x0C:
        case 0x0D:  {
            //  TODO: *** Separate timings for sequential, and non-sequential accesses.
            // waitstate 2

            if(!largeCart) {
                if(shift == 0x0D && cartSaveType == EEPROM_TYPE) {
                    eeprom.transferBitToEeprom(value & 0x1);
                }
            }

            if((address & 0x00FFFFFF) >= 0x00FFFF00 && cartSaveType == EEPROM_TYPE) {
                eeprom.transferBitToEeprom(value & 0x1);
            }

            switch(width) {
                case 32: {
                    //writeToArray32(&gamePakRom, address, 0x0C000000, value);
                    break;
                }
                case 16: {
                    //writeToArray16(&gamePakRom, address, 0x0C000000, value);            
                    break;
                }
                case 8: {
                    //writeToArray8(&gamePakRom, address, 0x0C000000, value);            
                    break;
                }
                default: {
                    assert(false);
                }
            }   
            break;
        }
        case 0x0E:
        case 0x0F:  {
            // The 64K SRAM area is mirrored across the whole 32MB area at E000000h-FFFFFFFh, 
            // also, inside of the 64K SRAM field, 32K SRAM chips are repeated twice.

            if(cartSaveType == FLASH512_TYPE || cartSaveType == FLASH1024_TYPE) {
                if(0x0E000000 <= address && address <= 0x0E00FFFF) {
                    flash.write(address, value);
                    break;
                }
                
            }

            address &= 0x00007FFF;

            switch(width) {
                case 32: {
                    value = ARM7TDMI::aluShiftRor(value, (address) * 8);
                    writeToArray8(&gamePakSram, address, 0x00000000, value); 
                    break;
                }
                case 16: {
                    value = ARM7TDMI::aluShiftRor(value, (address) * 8);
                    writeToArray8(&gamePakSram, address, 0x00000000, value); 
                    break;
                }
                case 8: {
                    writeToArray8(&gamePakSram, address, 0x00000000, value); 
                    break;           
                }
                default: {
                    assert(false);
                    break;
                }
            } 

            break;
        }
        default: {
            // TODO: implement unused memory access behaviour (and check for unused memory writes)
            break;
        }
    }
}

uint32_t Bus::view32(uint32_t address) {
    return view(address, 32);
}

uint32_t Bus::view(uint32_t address, uint8_t width) {
    // TODO avoid the code copying for this method

    /*
        TODO: The BIOS memory is protected against reading, 
        the GBA allows to read opcodes or data only if the program counter 
        is located inside of the BIOS area. If the program counter is not 
        in the BIOS area, reading will return the most recent successfully 
        fetched BIOS opcode (eg. the opcode at [00DCh+8] after startup and SoftReset, 
        the opcode at [0134h+8] during IRQ execution, and opcode at [013Ch+8] after 
        IRQ execution, and opcode at [0188h+8] after SWI execution).
        (see mgba tests)
    */

   uint32_t shift = (address & 0xFF000000) >> 24;

    switch(shift) {
        case 0x0:
        case 0x01: {
            
            if(0x00004000 <= address && address <= 0x01FFFFFF)  {
                break;
            }
            switch(width) {
                case 32: {
                    return readFromArray32(&bios, align32(address), 0);
                }
                case 16: {
                    return readFromArray16(&bios, align16(address), 0);
                }
                case 8: {
                    return readFromArray8(&bios, address, 0);
                }
                default: {
                    assert(false);
                    break;
                }
                break;
            }
            break;
        }
        case 0x02: { // board RAM 
            address &= 0x0203FFFF;
            switch(width) {
                case 32: {
                    return readFromArray32(&wRamBoard, align32(address), 0x02000000);
                }
                case 16: {
                    return readFromArray16(&wRamBoard, align16(address), 0x02000000);            
                }
                case 8: {
                    return readFromArray8(&wRamBoard, address, 0x02000000);            
                }
                default: {
                    assert(false);
                    break;
                }
            }
            break;
        }   
        case 0x03: {   
            // mirrored every 8000 bytes
            address &= 0x03007FFF;
            if(address > 0x03007FFF) {
                break;
            }
            switch(width) {
                case 32: {
                    return readFromArray32(&wRamChip, align32(address), 0x03000000);
                }
                case 16: {
                    return readFromArray16(&wRamChip, align16(address), 0x03000000);            
                }
                case 8: {
                    return readFromArray8(&wRamChip, address, 0x03000000);            
                }
                default: {
                    assert(false);
                    break;
                }
            }     
            break;
        }      
        case 0x04: {
            if(address > 0x040003FE) {
                // TODO: handle strange io mem accesses
                break;
            }

            switch(width) {
                case 32: {
                    return readFromArray32(&iORegisters, align32(address), 0x04000000);
                }
                case 16: {
                    return readFromArray16(&iORegisters, align16(address), 0x04000000);            
                }
                case 8: {
                    return readFromArray8(&iORegisters, address, 0x04000000);            
                }
                default: {
                    assert(false);
                    break;
                }
            }    
            break;       
        }     
        case 0x05: {  
            // if(address > 0x050003FF) {
            //     break;
            // }  
            address &= 0x050003FF;
            switch(width) {
                case 32: {
                    return readFromArray32(&paletteRam, align32(address), 0x05000000);
                }
                case 16: {
                    return readFromArray16(&paletteRam, align16(address), 0x05000000);            
                }
                case 8: {
                    return readFromArray8(&paletteRam, address, 0x05000000);            
                }
                default: {
                    assert(false);
                    break;
                }
            } 
            break;
        }    
        case 0x06: {    
            // Even though VRAM is sized 96K (64K+32K), it is repeated in steps of 128K 
            // (64K+32K+32K, the two 32K blocks itself being mirrors of each other).
            //address &= 0x06017FFF;
            if(address & 0x00010000) {
                address &= 0x06007FFF;
                address += 0x10000;
            } else {
                address &= 0x0600FFFF;
            }
            switch(width) {
                case 32: {
                    return readFromArray32(&vRam, align32(address), 0x06000000);
                }
                case 16: {
                    return readFromArray16(&vRam, align16(address), 0x06000000);            
                }
                case 8: {
                    return readFromArray8(&vRam, address, 0x06000000);            
                }
                default: {
                    assert(false);
                    break;
                }
            }    
            break;
        }        
        case 0x07: {   
            address &= 0x070003FF;
            switch(width) {
                case 32: {
                    return readFromArray32(&objAttributes, align32(address), 0x07000000);
                }
                case 16: {
                    return readFromArray16(&objAttributes, align16(address), 0x07000000);            
                }
                case 8: {
                    return readFromArray8(&objAttributes, address, 0x07000000);           
                }
                default: {
                    assert(false);
                    break;
                }
            }
            break;
        }      
        case 0x08:
        case 0x09: {
            //  TODO: *** Separate timings for sequential, and non-sequential accesses.
            // waitstate 0 
            address &= 0x00FFFFFF;
            switch(width) {
                case 32: {
                    return readFromArray32(&gamePakRom, align32(address), 0x00000000);
                }
                case 16: {
                    return readFromArray16(&gamePakRom, align16(address), 0x00000000);            
                }
                case 8: {  
                    return readFromArray8(&gamePakRom, address, 0x00000000);           
                }
                default: {
                    assert(false);
                    break;
                }
            } 
            break;  
        } 
        case 0x0A:
        case 0x0B: {
            //  TODO: *** Separate timings for sequential, and non-sequential accesses.
            // waitstate 1
            address &= 0x00FFFFFF;
            switch(width) {
                case 32: {
                    return readFromArray32(&gamePakRom, align32(address), 0x00000000);
                }
                case 16: {
                    return readFromArray16(&gamePakRom, align16(address), 0x00000000);            
                }
                case 8: {
                    return readFromArray8(&gamePakRom, address, 0x00000000);            
                }
                default: {
                    assert(false);
                    break;
                }
            }   
            break;
        } 
        case 0x0C:
        case 0x0D:  {
            //  TODO: *** Separate timings for sequential, and non-sequential accesses.
            // waitstate 2
            address &= 0x00FFFFFF;
            switch(width) {
                case 32: {
                    return readFromArray32(&gamePakRom, align32(address), 0x00000000);
                }
                case 16: {
                    return readFromArray16(&gamePakRom, align16(address), 0x00000000);           
                }
                case 8: {
                    return readFromArray8(&gamePakRom, address, 0x00000000);            
                }
                default: {
                    assert(false);
                    break;
                }
            } 
            break;  
        }
        case 0x0E:
        case 0x0F:  {
            // The 64K SRAM area is mirrored across the whole 32MB area at E000000h-FFFFFFFh, 
            // also, inside of the 64K SRAM field, 32K SRAM chips are repeated twice.
            address &= 0x00007FFF;

            switch(width) {
                case 32: {
                    return (((uint32_t)readFromArray8(&gamePakSram, address, 0x00000000)) * 0x01010101);      
                }
                case 16: {
                    return ((uint32_t)readFromArray8(&gamePakSram, address, 0x00000000)) * 0x0101;    
                }
                case 8: {
                    return readFromArray8(&gamePakSram, address, 0x00000000);           
                }
                default: {
                    assert(false);
                    break;
                }
            } 

            break;
        }
        default: {  
            // TODO: implement unused memory access behaviour (and check for unused memory writes)
            break;
        }

    }
    return 436207618U;
}

void Bus::loadRom(std::vector<uint8_t> &buffer) {
    // TODO: assert that roms are smaller than 32MB

    std::string s{buffer.begin(), buffer.end()};
    std::regex eepromRegex = std::regex{"EEPROM_V\\d\\d\\d"};
    std::regex sramRegex = std::regex{"SRAM_V\\d\\d\\d"};
    std::regex flashRegex = std::regex{"FLASH_V\\d\\d\\d"};
    std::regex flash512Regex = std::regex{"FLASH512_V\\d\\d\\d"};
    std::regex flash1MbRegex = std::regex{"FLASH1M_V\\d\\d\\d"};

    if(std::regex_search(s, eepromRegex)) {

        cartSaveType = Bus::CartSaveType::EEPROM_TYPE;
        std::cout << "eeprom save type\n";
        dma->eepromBusWidthDetected = false;
    } else if(std::regex_search(s, sramRegex)) {

        cartSaveType = Bus::CartSaveType::SRAM_TYPE;
        std::cout << "sram save type\n";
    } else if(std::regex_search(s, flashRegex)) {

        flash.setSize(512);
        cartSaveType = Bus::CartSaveType::FLASH512_TYPE;
        std::cout << "flash512 save type\n";
    } else if(std::regex_search(s, flash512Regex)) {

        flash.setSize(512);
        cartSaveType = Bus::CartSaveType::FLASH512_TYPE;
        std::cout << "flash512 save type\n";
    } else if(std::regex_search(s, flash1MbRegex)) {

        flash.setSize(1024);
        cartSaveType = Bus::CartSaveType::FLASH1024_TYPE;
        std::cout << "flash1024 save type\n";
    } else {

        DEBUGWARN("cartridge save type could not be detected\n");
        cartSaveType = Bus::CartSaveType::SRAM_TYPE;
    }

    largeCart = (buffer.size() > 0x1000000);
    if(largeCart) {
        std::cout << "large cartridge\n";
    }

    for (int i = 0; i < buffer.size(); i++) {
        gamePakRom[i] = buffer[i];
    }
}


uint8_t Bus::getCurrentNWaitstate() {
    return currentNWaitstate;
}

uint8_t Bus::getCurrentSWaitstate() {
    return currentSWaitstate;
}


void Bus::write32(uint32_t address, uint32_t word, CycleType accessType) {
    write(address, word, 32, accessType);
}

uint32_t Bus::read32(uint32_t address, CycleType accessType) {
    return read(address, 32, accessType);
}

void Bus::write16(uint32_t address, uint16_t halfWord, CycleType accessType) {
    write(address, halfWord, 16, accessType);
}

uint16_t Bus::read16(uint32_t address, CycleType accessType) {
    return read(address, 16, accessType);
}

void Bus::write8(uint32_t address, uint8_t byte, CycleType accessType) {
    write(address, byte, 8, accessType);
}

uint8_t Bus::read8(uint32_t address, CycleType accessType) {
    return read(address, 8, accessType);
}


uint8_t Bus::getWaitState0NCycles() {
    return waitStateNVals[(iORegisters.at(waitcntOffset) & 0x0000000C) >> 2];
}
uint8_t Bus::getWaitState1NCycles() {
    return waitStateNVals[(iORegisters.at(waitcntOffset) & 0x00000060) >> 5];
}
uint8_t Bus::getWaitState2NCycles() {
    return waitStateNVals[(iORegisters.at(waitcntOffset) & 0x00000300) >> 8];
}

uint8_t Bus::getWaitState0SCycles() {
    return waitState0SVals[(iORegisters.at(waitcntOffset) & 0x00000010) >> 4];
}
uint8_t Bus::getWaitState1SCycles() {
    return waitState1SVals[(iORegisters.at(waitcntOffset) & 0x00000080) >> 7];
}
uint8_t Bus::getWaitState2SCycles() {
    return waitState2SVals[(iORegisters.at(waitcntOffset) & 0x00000400) >> 10];
}

void Bus::resetCycleCountTimeline() {
    //currentNWaitstate = 1;
    //currentSWaitstate = 1;
    executionTimelineSize = 0;
    memAccessCycles = 0;
}

uint32_t Bus::getMemoryAccessCycles() {
    return memAccessCycles;
}

void Bus::printCurrentExecutionTimeline() {
    std::cout << "[";
    for(int i = 0; i < executionTimelineSize; i++) {
        std::cout << "(" << cycleTypesSerialized[executionTimelineCycleType[i]] << ","
                         << (uint32_t)executionTimelineCycles[i] << "),";
    
    }
    std::cout << "]\n";
}

void Bus::connectTimer(std::shared_ptr<Timer> _timer) {
    this->timer = _timer;
}

void Bus::connectDma(std::shared_ptr<DMA> _dma) {
    this->dma = _dma;
}

void Bus::connectPpu(std::shared_ptr<PPU> _ppu) {
    this->ppu = _ppu;
}

// TODO: can make static ?
bool Bus::isAddressInEeprom(uint32_t address) {
    if((address & 0xFF000000) < 0x08000000 || (address & 0xFF000000) > 0x0D000000) {
        return false;
    }
    if(largeCart) {
        uint32_t mask = address & 0x00FFFFFF;
        if(0x00FFFF00 <= mask && mask <= 0x00FFFFFF) {
            return true;
        } 
    } else {
        uint32_t mask = address & 0x00FFFFFF;
        if((0x00FFFF00 <= mask && mask <= 0x00FFFFFF) || (address >= 0x0D000000)) {
            return true;
        } 
    } 
    return false;
}


void Bus::setEepromBusWidth(uint32_t width) {
    assert(width == 6 || width == 14);

    if(width == 6) {
        eeprom.setBusWidth(6);
    } else {    
        // width = 14
        eeprom.setBusWidth(14);
    }
}



<|start_filename|>src/arm7tdmi/ARMInstructions/ArmSdsHandler.h<|end_filename|>

#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"


template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::armSdsHandler(uint32_t instruction, ARM7TDMI* cpu) {
    // TODO: figure out memory alignment logic (for all data transfer ops)
    // (verify against existing CPU implementations
    constexpr bool b = op & 0x040;
    assert((instruction & 0x0F800000) == 0x01000000);
    assert(!(instruction & 0x00300000));
    assert((instruction & 0x00000FF0) == 0x00000090);
    uint8_t rn = getRn(instruction);
    uint8_t rd = getRd(instruction);
    uint8_t rm = getRm(instruction);
    assert((rn != 15) && (rd != 15) && (rm != 15));

    // SWP{cond}{B} Rd,Rm,[Rn]     ;Rd=[Rn], [Rn]=Rm
    if constexpr(b) {
        // SWPB swap byte
        uint32_t rnVal = cpu->getRegister(rn);
        uint8_t rmVal = (uint8_t)(cpu->getRegister(rm));
        uint8_t data = cpu->bus->read8(rnVal, Bus::CycleType::NONSEQUENTIAL);
        cpu->setRegister(rd, (uint32_t)data);
        // TODO: check, is it a zero-extended 32-bit write or an 8-bit write?
        cpu->bus->write8(rnVal, rmVal, Bus::CycleType::NONSEQUENTIAL);
    } else {
        // SWPB swap word
        // The SWP opcode works like a combination of LDR and STR, that means, 
        // it does read-rotated, but does write-unrotated.
        uint32_t rnVal = cpu->getRegister(rn);
        uint32_t rmVal = cpu->getRegister(rm);
        
        uint32_t data = aluShiftRor(cpu->bus->read32(rnVal, Bus::CycleType::NONSEQUENTIAL), (rnVal & 3) * 8);
        cpu->setRegister(rd, data);
        cpu->bus->write32(rnVal, rmVal, Bus::CycleType::NONSEQUENTIAL);
    }
    cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
    return NONSEQUENTIAL;

}

<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbAluHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

// op = instruction & 0xFFC0;
// anything within the mask can be constexpr!

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbAluHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xFC00) == 0x4000);
    //uint8_t opcode = (instruction & 0x03C0) >> 6;
    constexpr uint8_t opcode = (op & 0x00F);

    uint8_t rs = thumbGetRs(instruction);
    uint8_t rd = thumbGetRd(instruction);
    bool carryFlag, overflowFlag, signFlag, zeroFlag;

    Cycles cycles;
    int internalCycles = 0;


    if constexpr(opcode == 0x0) {
        // 0: AND{S} Rd,Rs     ;AND logical       Rd = Rd AND Rs
        uint32_t rsVal = cpu->getRegister(rs);
        uint32_t rdVal = cpu->getRegister(rd);
        uint32_t result;
        result = rdVal & rsVal;
        signFlag = aluSetsSignBit(result);
        zeroFlag = aluSetsZeroBit(result);
        carryFlag = cpu->cpsr.C;
        overflowFlag = cpu->cpsr.V;
        cpu->setRegister(rd, result);
    } else if constexpr(opcode == 0x1) {
        //  1: EOR{S} Rd,Rs     ;XOR logical       Rd = Rd XOR Rs
        uint32_t rsVal = cpu->getRegister(rs);
        uint32_t rdVal = cpu->getRegister(rd);
        uint32_t result;
        result = rdVal ^ rsVal;
        signFlag = aluSetsSignBit(result);
        zeroFlag = aluSetsZeroBit(result);
        carryFlag = cpu->cpsr.C;
        overflowFlag = cpu->cpsr.V;
        cpu->setRegister(rd, result);
    } else if constexpr(opcode == 0x2) {
        // 2: LSL{S} Rd,Rs     ;log. shift left   Rd = Rd << (Rs AND 0FFh)
        uint32_t rsVal = cpu->getRegister(rs);
        uint32_t rdVal = cpu->getRegister(rd);
        uint8_t offset = rsVal & 0xFF;
        uint32_t result;
        result = aluShiftLsl(rdVal, offset);
        signFlag = aluSetsSignBit(result);
        zeroFlag = aluSetsZeroBit(result);
        // TODO:
        if(!offset) {
            carryFlag = cpu->cpsr.C;
        } else {
            carryFlag = aluLslSetsCarryBit(rdVal, offset);
        }
        overflowFlag = cpu->cpsr.V;
        cpu->setRegister(rd, result);
        internalCycles = 1;
    } else if constexpr(opcode == 0x3) {
        // 3: LSR{S} Rd,Rs     ;log. shift right  Rd = Rd >> (Rs AND 0FFh)
        uint32_t rsVal = cpu->getRegister(rs);
        uint32_t rdVal = cpu->getRegister(rd);
        uint8_t offset = rsVal & 0xFF;
        uint32_t result;
        result = aluShiftLsr(rdVal, offset);
        signFlag = aluSetsSignBit(result);
        zeroFlag = aluSetsZeroBit(result);
        // TODO:
        carryFlag = !(offset) ? cpu->cpsr.C : aluLsrSetsCarryBit(rdVal, offset);
        overflowFlag = cpu->cpsr.V;
        cpu->setRegister(rd, result);
        internalCycles = 1;
    } else if constexpr(opcode == 0x4) {
        // 4: ASR{S} Rd,Rs     ;arit shift right  Rd = Rd SAR (Rs AND 0FFh)
        uint32_t rsVal = cpu->getRegister(rs);
        uint32_t rdVal = cpu->getRegister(rd);
        uint8_t offset = rsVal & 0xFF;
        uint32_t result;
        result = aluShiftAsr(rdVal, offset);
        signFlag = aluSetsSignBit(result);
        zeroFlag = aluSetsZeroBit(result);
        // TODO:
        carryFlag = !(offset) ? (cpu->cpsr.C) : aluAsrSetsCarryBit(rdVal, offset);
        overflowFlag = cpu->cpsr.V;
        cpu->setRegister(rd, result);
        internalCycles = 1;        
    } else if constexpr(opcode == 0x5) {
        // 5: ADC{S} Rd,Rs     ;add with carry    Rd = Rd + Rs + Cy
        uint64_t rsVal = cpu->getRegister(rs);
        uint64_t rdVal = cpu->getRegister(rd);
        uint64_t result;
        result = rdVal + rsVal + (uint64_t)(cpu->cpsr.C);
        signFlag = aluSetsSignBit((uint32_t)result);
        zeroFlag = aluSetsZeroBit((uint32_t)result);
        carryFlag = aluAddWithCarrySetsCarryBit(result);
        overflowFlag =
            aluAddWithCarrySetsOverflowBit(rdVal, rsVal, result, cpu);
        cpu->setRegister(rd, result);    
    } else if constexpr(opcode == 0x6) {
            // 6: SBC{S} Rd,Rs     ;sub with carry    Rd = Rd - Rs - NOT Cy
        uint64_t rsVal = cpu->getRegister(rs);
        uint64_t rdVal = cpu->getRegister(rd);
        uint64_t result;
        result = rdVal - rsVal - !(uint64_t)(cpu->cpsr.C);
        signFlag = aluSetsSignBit((uint32_t)result);
        zeroFlag = aluSetsZeroBit((uint32_t)result);
        carryFlag = aluSubWithCarrySetsCarryBit(result);
        overflowFlag = aluSubWithCarrySetsOverflowBit(rdVal, rsVal, result, cpu);
        cpu->setRegister(rd, result);  
    } else if constexpr(opcode == 0x7) {
        // 7: ROR{S} Rd,Rs     ;rotate right      Rd = Rd ROR (Rs AND 0FFh)
        uint32_t rsVal = cpu->getRegister(rs);
        uint32_t rdVal = cpu->getRegister(rd);
        uint8_t offset = rsVal & 0xFF;
        uint32_t result;
        result = aluShiftRor(rdVal, offset % 32);
        signFlag = aluSetsSignBit(result);
        zeroFlag = aluSetsZeroBit(result);
        overflowFlag = cpu->cpsr.V;
        // TODO:
        carryFlag = !(offset) ? (cpu->cpsr.C) : aluRorSetsCarryBit(rdVal, offset);
        cpu->setRegister(rd, result);
        internalCycles = 1;        
    } else if constexpr(opcode == 0x8) {
        // 8: TST    Rd,Rs     ;test            Void = Rd AND Rs
        uint32_t rsVal = cpu->getRegister(rs);
        uint32_t rdVal = cpu->getRegister(rd);
        uint32_t result;
        result = rdVal & rsVal;
        signFlag = aluSetsSignBit(result);
        zeroFlag = aluSetsZeroBit(result);
        carryFlag = cpu->cpsr.C;
        overflowFlag = cpu->cpsr.V;        
    } else if constexpr(opcode == 0x9) {
        // 9: NEG{S} Rd,Rs     ;negate            Rd = 0 - Rs
        uint32_t rsVal = cpu->getRegister(rs);
        uint32_t result;
        result = (uint32_t)0 - rsVal;
        signFlag = aluSetsSignBit((uint32_t)result);
        zeroFlag = aluSetsZeroBit((uint32_t)result);
        carryFlag = aluSubtractSetsCarryBit(0, rsVal);
        overflowFlag = aluSubtractSetsOverflowBit(0, rsVal, result);
        cpu->setRegister(rd, result);       
    } else if constexpr(opcode == 0xA) {
        // A: CMP    Rd,Rs     ;compare         Void = Rd - Rs
        uint32_t rsVal = cpu->getRegister(rs);
        uint32_t rdVal = cpu->getRegister(rd);
        uint32_t result;
        result = rdVal - rsVal;
        signFlag = aluSetsSignBit((uint32_t)result);
        zeroFlag = aluSetsZeroBit((uint32_t)result);
        carryFlag = aluSubtractSetsCarryBit(rdVal, rsVal);
        overflowFlag = aluSubtractSetsOverflowBit(rdVal, rsVal, result);        
    } else if constexpr(opcode == 0xB) {
        // B: CMN    Rd,Rs     ;neg.compare     Void = Rd + Rs
        uint32_t rsVal = cpu->getRegister(rs);
        uint32_t rdVal = cpu->getRegister(rd);
        uint32_t result;
        result = rdVal + rsVal;
        signFlag = aluSetsSignBit((uint32_t)result);
        zeroFlag = aluSetsZeroBit((uint32_t)result);
        carryFlag = aluAddSetsCarryBit(rdVal, rsVal);
        overflowFlag = aluAddSetsOverflowBit(rdVal, rsVal, result);        
    } else if constexpr(opcode == 0xC) {
        // C: ORR{S} Rd,Rs     ;OR logical        Rd = Rd OR Rs
        uint32_t rsVal = cpu->getRegister(rs);
        uint32_t rdVal = cpu->getRegister(rd);
        uint32_t result;
        result = rdVal | rsVal;
        signFlag = aluSetsSignBit(result);
        zeroFlag = aluSetsZeroBit(result);
        carryFlag = cpu->cpsr.C;
        overflowFlag = cpu->cpsr.V;
        cpu->setRegister(rd, result);        
    } else if constexpr(opcode == 0xD) {
        // D: MUL{S} Rd,Rs     ;multiply          Rd = Rd * Rs
        uint64_t rsVal = cpu->getRegister(rs);
        uint64_t rdVal = cpu->getRegister(rd);
        uint64_t result;
        result = rdVal * rsVal;
        signFlag = aluSetsSignBit(result);
        zeroFlag = aluSetsZeroBit(result);
        // carry flag destroyed
        // TODO: why? it says on GBATek ARMv4 and below: carry flag destroyed
        carryFlag = cpu->cpsr.C;
        // carryFlag = 0;
        overflowFlag = cpu->cpsr.V;
        cpu->setRegister(rd, result);
        internalCycles = mulGetExecutionTimeMVal(rdVal);  
    } else if constexpr(opcode == 0xE) {
        // E: BIC{S} Rd,Rs     ;bit clear         Rd = Rd AND NOT Rs

        uint32_t rsVal = cpu->getRegister(rs);
        uint32_t rdVal = cpu->getRegister(rd);
        uint32_t result;
        result = rdVal & (~rsVal);
        signFlag = aluSetsSignBit(result);
        zeroFlag = aluSetsZeroBit(result);
        carryFlag = (cpu->cpsr.C);
        overflowFlag = cpu->cpsr.V;
        cpu->setRegister(rd, result);   
    } else {
        // opcode == 0xF
        // F: MVN{S} Rd,Rs     ;not               Rd = NOT Rs
        uint32_t rsVal = cpu->getRegister(rs);
        uint32_t result;
        result = ~rsVal;
        signFlag = aluSetsSignBit(result);
        zeroFlag = aluSetsZeroBit(result);
        carryFlag = cpu->cpsr.C;
        overflowFlag = cpu->cpsr.V;
        cpu->setRegister(rd, result);
    }

    cpu->cpsr.N = signFlag;
    cpu->cpsr.Z = zeroFlag;
    cpu->cpsr.C = carryFlag;
    cpu->cpsr.V = overflowFlag;

    for(int i = 0; i < internalCycles; i++) {
        cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
    }
    if(!internalCycles) {
        return SEQUENTIAL;
    } else {
        return NONSEQUENTIAL;
    }
}


<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbRelAddrHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbRelAddrHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xF000) == 0xA000);
    //uint8_t opcode = (instruction & 0x0800) >> 11;
    constexpr bool opcode = (op & 0x020);
    //uint8_t rd = (instruction & 0x0700) >> 8;
    constexpr uint8_t rd = (op & 0x01C) >> 2;
    uint16_t offset = (instruction & 0x00FF) << 2;

    if constexpr(!opcode) {
        // 0: ADD  Rd,PC,#nn    ;Rd = (($+4) AND NOT 2) + nn
        // TODO: why do we have to use +2 to pass the tests, instead of the +4 in gbatek?
        uint32_t value = ((cpu->getRegister(PC_REGISTER) + 2) & (~2)) + offset;
        cpu->setRegister(rd, value);
    } else {
        // 1: ADD  Rd,SP,#nn    ;Rd = SP + nn
        uint32_t value = cpu->getRegister(SP_REGISTER) + offset;
        cpu->setRegister(rd, value);
    }

    return SEQUENTIAL;
}


<|start_filename|>src/LCD.h<|end_filename|>
#include <SFML/Graphics.hpp>
#include <vector>
#include <memory>

class LCD {

    public: 
        void initWindow();
        void drawWindow(std::array<uint16_t, 38400 /* width x height */>& pixelBuffer);
        void closeWindow();

    private: 
        static void drawPixel();
        std::shared_ptr<sf::RenderWindow> gbaWindow;
        sf::VertexArray pixels;
        sf::Event event;
        int defaultScreenSize = 7;
};

<|start_filename|>src/Debugger.h<|end_filename|>

#include <cstdint>
#include <array>
#include <string>
#include <capstone.h>

class ARM7TDMI;
class Bus;

// TODO: very quick and rough debugger. In the future make it more sophisticated and make a UI 
class Debugger {

    public: 
        Debugger();

        void updateState(ARM7TDMI* cpu, Bus* bus);
        void printState();
        void step(ARM7TDMI* cpu, Bus* bus);
        static bool stepMode;
        std::string disassembleArm(uint32_t instruction);

    private:

        csh sCapstone;
        
        uint32_t watchAddr1 = 0x04000208;
        uint8_t watchAddr1Width = 32;

        uint32_t watchAddr2 = 0x04000200;
        uint8_t watchAddr2Width = 8;

        uint32_t watchAddr3 = 0x04000202;
        uint8_t watchAddr3Width = 8;

        uint32_t watchAddr4 = 0x00000000;
        uint8_t watchAddr4Width = 8;

        uint32_t watchAddr5 = 0x00000000;
        uint8_t watchAddr5Width = 8;

        uint32_t watchAddr6 = 0x00000000;
        uint8_t watchAddr6Width = 8;

        uint32_t r0; 
        uint32_t r1; 
        uint32_t r2; 
        uint32_t r3; 
        uint32_t r4; 
        uint32_t r5; 
        uint32_t r6; 
        uint32_t r7;
        uint32_t r8; 
        uint32_t r9; 
        uint32_t r10;
        uint32_t r11;
        uint32_t r12;
        uint32_t r13;
        uint32_t r14;
        uint32_t r15;
        uint32_t r13irq;
        uint32_t r14irq;
        uint32_t r13svc;
        uint32_t r14svc;
        uint32_t r13und;
        uint32_t r14und;
        uint32_t watchMem1; 
        uint32_t cpsr;
        uint32_t watchMem2;
        uint32_t spsr_svc; 
        uint32_t watchMem3;
        uint32_t spsr_fiq;
        uint32_t watchMem4;
        uint32_t spsr_abt;
        uint32_t watchMem5;
        uint32_t spsr_irq;
        uint32_t watchMem6;
        uint32_t spsr_und; 
        uint32_t instrAddress; 
        std::string instr;
        uint32_t instrWord;

        uint32_t breakpoint = 0x08000B84;
};

<|start_filename|>src/memory/Bus.h<|end_filename|>
#pragma once


#include <array>
#include <vector>
#include <string>
#include <memory>
#include "EEPROM.h"
#include "Flash.h"

//#define LARGE_CARTRIDGE 1;
#define FLASH_CART 1;

class PPU;
class Timer;
class ARM7TDMI;
class DMA;

class Bus {
    // TODO: implement an OPEN BUS (ie if retreiving invalid mem location, return value last on bus)
    
    // TODO: NOTES: The GBA forcefully uses non-sequential timing at the beginning of each 128K-block of gamepak ROM, 
    // eg. "LDMIA [801fff8h],r0-r7" will have non-sequential timing at 8020000h.

   public:
    Bus();
    ~Bus();

    void connectTimer(std::shared_ptr<Timer> timer);
    void connectDma(std::shared_ptr<DMA> dma);
    void connectPpu(std::shared_ptr<PPU> ppu);

    enum CycleType {
        SEQUENTIAL,
        NONSEQUENTIAL,
        INTERNAL
    };

    enum IORegister {
        IE = 0x04000200 - 0x04000000, // Interrupt Enable Register
        IF = 0x04000202 - 0x04000000, // Interrupt Request Flags / IRQ Acknowledge
        IME = 0x04000208 - 0x04000000, // Interrupt Master Enable Register
        DISPCNT = 0x04000000 - 0x04000000, // LCD control
        DISPSTAT = 0x04000004 - 0x04000000,  // General LCD Status (STAT,LYC)
        VCOUNT = 0x04000006 - 0x04000000, // Vertical Counter (LY)
        KEYINPUT = 0x04000130 - 0x04000000, // KEYINPUT - Key Status (R)
        KEYCNT = 0x04000132 - 0x04000000, // R/W  KEYCNT    Key Interrupt Control

        DMA0SAD = 0x040000B0 - 0x04000000, // DMA 0 Source Address
        DMA0DAD = 0x040000B4 - 0x04000000, // DMA 0 Destination Address
        DMA0CNT_L = 0x040000B8 - 0x04000000, // DMA 0 Word Count
        DMA0CNT_H = 0x040000BA - 0x04000000, // DMA 0 Control
        DMA1SAD = 0x040000BC - 0x04000000, // DMA 1 Source Address
        DMA1DAD = 0x040000C0 - 0x04000000, // DMA 1 Destination Address
        DMA1CNT_L = 0x040000C4 - 0x04000000, // DMA 1 Word Count
        DMA1CNT_H = 0x040000C6 - 0x04000000, // DMA 1 Control
        DMA2SAD = 0x040000C8 - 0x04000000, // DMA 2 Source Address
        DMA2DAD = 0x040000CC - 0x04000000, // DMA 2 Destination Address
        DMA2CNT_L = 0x040000D0 - 0x04000000, // DMA 2 Word Count
        DMA2CNT_H = 0x040000D2 - 0x04000000, // DMA 2 Control
        DMA3SAD = 0x040000D4 - 0x04000000, // DMA 3 Source Address
        DMA3DAD = 0x040000D8 - 0x04000000, // DMA 3 Destination Address
        DMA3CNT_L = 0x040000DC - 0x04000000, // DMA 3 Word Count
        DMA3CNT_H = 0x040000DE - 0x04000000, // DMA 3 Control

        WIN0H = 0x04000040 - 0x04000000, // Window 0 Horizontal Dimensions (W)
        WIN1H = 0x04000042 - 0x04000000, // Window 1 Horizontal Dimensions (W)
        WIN0V = 0x04000044 - 0x04000000, // Window 0 Vertical Dimensions (W)
        WIN1V = 0x04000046 - 0x04000000, // Window 1 Vertical Dimensions (W)
        WININ = 0x04000048 - 0x04000000, // WININ - Control of Inside of Window(s) (R/W)
        WINOUT = 0x0400004A - 0x04000000, // Control of Outside of Windows & Inside of OBJ Window (R/W)

        TM0CNT_L = 0x04000100 - 0x04000000, // TM0CNT_L  Timer 0 Counter/Reload
        TM1CNT_L = 0x04000104 - 0x04000000, // TM0CNT_L  Timer 0 Counter/Reload
        TM2CNT_L = 0x04000108 - 0x04000000, // TM0CNT_L  Timer 0 Counter/Reload
        TM3CNT_L = 0x0400010C - 0x04000000, // TM0CNT_L  Timer 0 Counter/Reload

        /*
                4000800h - 32bit - Undocumented - Internal Memory Control (R/W)
                Supported by GBA and GBA SP only - NOT supported by DS (even in GBA mode).
                Also supported by GBA Micro - but crashes on "overclocked" WRAM setting.
                Initialized to 0D000020h (by hardware). Unlike all other I/O registers, this register is mirrored across the whole I/O area (in increments of 64K, ie. at 4000800h, 4010800h, 4020800h, ..., 4FF0800h)
                TODO: implement more logic for this
        */
        INTERNAL_MEM_CNT = 0x04000800 - 0x04000000,

        HALTCNT = 0x04000301 - 0x04000000   // HALTCNT - BYTE - Undocumented - Low Power Mode Control (W)

    };

    enum CartSaveType {
        FLASH512_TYPE,
        FLASH1024_TYPE,
        SRAM_TYPE,
        EEPROM_TYPE
    };

    CartSaveType cartSaveType;

    bool haltMode = false;

    /* General Internal Memory */

    // 00000000-00003FFF   BIOS - System ROM (16 KBytes) 16448
    std::vector<uint8_t> bios;
    // work ram! 02000000-0203FFFF (256kB) 263168
    std::vector<uint8_t> wRamBoard;
    // 03000000-03007FFF (32 kB) 32896
    std::vector<uint8_t> wRamChip;
    // 04000000-040003FE   I/O Registers 1028
    std::vector<uint8_t> iORegisters;

    /* Internal Display Memory */

    // 05000000-050003FF   BG/OBJ Palette RAM        (1 Kbyte) 1028
    std::vector<uint8_t> paletteRam;
    // 06000000-06017FFF   VRAM - Video RAM          (96 KBytes) 98688
    std::vector<uint8_t> vRam;
    // 07000000-070003FF   OAM - OBJ Attributes      (1 Kbyte) 1028
    // TODO: VRAM and Palette RAM may be accessed during H-Blanking. 
    // OAM can accessed only if "H-Blank Interval Free" bit in DISPCNT register is set.
    std::vector<uint8_t> objAttributes;

    /* External Memory (Game Pak) */

    // 08000000-09FFFFFF   Game Pak ROM/FlashROM (max 32MB) - Wait State 0
    // 0A000000-0BFFFFFF   Game Pak ROM/FlashROM (max 32MB) - Wait State 1
    // 0C000000-0DFFFFFF   Game Pak ROM/FlashROM (max 32MB) - Wait State 2
    std::vector<uint8_t> gamePakRom;
    // 0E000000-0E00FFFF   Game Pak SRAM    (max 64 KBytes) - 8bit Bus width (65792)
    std::vector<uint8_t> gamePakSram;


    uint32_t read32(uint32_t address, CycleType accessType);
    uint16_t read16(uint32_t address, CycleType accessType);
    uint8_t read8(uint32_t address, CycleType accessType);

    uint32_t view32(uint32_t address);

    void write32(uint32_t address, uint32_t word, CycleType accessType);
    void write16(uint32_t address, uint16_t halfWord, CycleType accessType);
    void write8(uint32_t address, uint8_t byte, CycleType accessType);

    void loadRom(std::vector<uint8_t> &buffer);

    uint8_t getCurrentNWaitstate();
    uint8_t getCurrentSWaitstate();

    // resets the execution step timeline. 
    void resetCycleCountTimeline();

    // adds eexecution step to timeline 
    // use if need to emualte extra exection steps besides the memory access
    void addCycleToExecutionTimeline(CycleType cycleType, uint32_t shift, uint8_t width);
    void printCurrentExecutionTimeline();

    void enterVBlank();
    void enterHBlank();
    void leaveVBlank();
    void leaveHBlank();

    bool ppuMemDirty = false;

    uint32_t getMemoryAccessCycles();

    bool isAddressInEeprom(uint32_t address);

    void setEepromBusWidth(uint32_t width);

   private:
    uint8_t currentNWaitstate;
    uint8_t currentSWaitstate;

    uint32_t view(uint32_t address, uint8_t width);

    uint32_t read(uint32_t address, uint8_t width, CycleType accessType);
    void write(uint32_t address, uint32_t value, uint8_t width, CycleType accessType);

    static constexpr uint32_t waitcntOffset = 0x204;

    uint8_t getWaitState0NCycles();
    uint8_t getWaitState1NCycles();
    uint8_t getWaitState2NCycles();

    uint8_t getWaitState0SCycles();
    uint8_t getWaitState1SCycles();
    uint8_t getWaitState2SCycles();

    uint8_t waitStateNVals[4] = {4,3,2,8};

    uint8_t waitState0SVals[2] = {2,1};
    uint8_t waitState1SVals[2] = {4,1};
    uint8_t waitState2SVals[2] = {8,1};

    std::array<std::string, 3> cycleTypesSerialized = {"S", "N", "I"};

    uint8_t executionTimelineSize = 0;
    std::array<uint8_t, 32> executionTimelineCycles;
    // TODO: questionable wither this should be owned by bus, could move into a scheduler class?
    std::array<CycleType, 32> executionTimelineCycleType;

    uint32_t memAccessCycles = 0;

    std::shared_ptr<PPU> ppu;
    std::shared_ptr<Timer> timer; 
    std::shared_ptr<DMA> dma;
    EEPROM eeprom;
    Flash flash;

    static uint32_t align32(uint32_t address);
    static uint32_t align16(uint32_t address);

    bool largeCart = false;

 };

 inline
 uint32_t Bus::align32(uint32_t address) {
     return address & 0xFFFFFFFC;
 }

 inline
 uint32_t Bus::align16(uint32_t address) {
     return address & 0xFFFFFFFE;
 } 

<|start_filename|>include/GameBoyAdvance.hpp<|end_filename|>
#include <string>
#include <memory>

class GameBoyAdvanceImpl;

class GameBoyAdvance {
    public: 
        GameBoyAdvance();
        bool loadRom(std::string path);
        void setBreakpoint(uint32_t address);
        void enableDebugger();
        void runRom(); 
        void printCpuState();
        // TODO: more public methods   
    
    private: 
        std::shared_ptr<GameBoyAdvanceImpl> pimpl;

};

<|start_filename|>src/LCD.cpp<|end_filename|>
#include "LCD.h"
// TODO: common include file for defines (like DEBUG)
#include "arm7tdmi/ARM7TDMI.h"
#include "PPU.h"

/**
 * Helper function for changing resolution
 * TODO: move this somewere else
 * */
void changeResolution(sf::VertexArray& pixels, float xRes, float yRes) {
    float aspectRatio = (float)(PPU::SCREEN_WIDTH) / (float)(PPU::SCREEN_HEIGHT); // 1.5
    float newAspectRatio = (xRes / yRes);

    float xScale = xRes / PPU::SCREEN_WIDTH;
    float yScale = yRes / PPU::SCREEN_HEIGHT;

    if(newAspectRatio <= aspectRatio) {
        // xRes is limiting scale factor
        float yShift = yRes/2.0 - (PPU::SCREEN_HEIGHT * xScale / 2.0);
        for(int x = 0; x < PPU::SCREEN_WIDTH; x++) {
            for(int y = 0; y < PPU::SCREEN_HEIGHT; y++) {  
                sf::Vertex* quad = &pixels[(y * PPU::SCREEN_WIDTH + x) * 4];
                quad[0].position = sf::Vector2f(x * xScale, y * xScale + yShift);
                quad[1].position = sf::Vector2f((x + 1) * xScale, y * xScale + yShift);
                quad[2].position = sf::Vector2f((x + 1) * xScale, (y + 1) * xScale + yShift);
                quad[3].position = sf::Vector2f(x * xScale, (y + 1) * xScale + yShift);
            }
        }
    } else {
        float xShift = xRes/2.0 - (PPU::SCREEN_WIDTH * yScale / 2.0);
        for(int x = 0; x < PPU::SCREEN_WIDTH; x++) {
            for(int y = 0; y < PPU::SCREEN_HEIGHT; y++) {  
                sf::Vertex* quad = &pixels[(y * PPU::SCREEN_WIDTH + x) * 4];
                quad[0].position = sf::Vector2f(x * yScale + xShift, y * yScale);
                quad[1].position = sf::Vector2f((x + 1) * yScale + xShift, y * yScale);
                quad[2].position = sf::Vector2f((x + 1) * yScale + xShift, (y + 1) * yScale);
                quad[3].position = sf::Vector2f(x * yScale + xShift, (y + 1) * yScale);
            }
        }
    }
}

void LCD::initWindow() {
    gbaWindow = std::make_shared<sf::RenderWindow>(sf::VideoMode(PPU::SCREEN_WIDTH * defaultScreenSize, 
                                                   PPU::SCREEN_HEIGHT * defaultScreenSize), 
                                                   "gba-mu");
    pixels.resize(PPU::SCREEN_WIDTH * PPU::SCREEN_HEIGHT * 4);
    pixels.setPrimitiveType(sf::Quads);

    for(int x = 0; x < PPU::SCREEN_WIDTH; x++) {
        for(int y = 0; y < PPU::SCREEN_HEIGHT; y++) {  
            sf::Vertex* quad = &pixels[(y * PPU::SCREEN_WIDTH + x) * 4];
            sf::Color color = sf::Color(rand());
            quad[0].color = color;
            quad[1].color = color;
            quad[2].color = color;
            quad[3].color = color;

            quad[0].position = sf::Vector2f(x, y);
            quad[1].position = sf::Vector2f(x + 1, y);
            quad[2].position = sf::Vector2f(x + 1, y + 1);
            quad[3].position = sf::Vector2f(x, y + 1);
        }
    }

    sf::FloatRect visibleArea(0, 0, PPU::SCREEN_WIDTH * defaultScreenSize, 
                                    PPU::SCREEN_HEIGHT * defaultScreenSize);

    sf::View view = sf::View(visibleArea);
    gbaWindow->setView(view);
    changeResolution(pixels, (float)(PPU::SCREEN_WIDTH * defaultScreenSize), 
                             (float)(PPU::SCREEN_HEIGHT * defaultScreenSize));

    gbaWindow->clear(sf::Color::Black);
    gbaWindow->display();
}

/*
  0-4   Red Intensity   (0-31)
  5-9   Green Intensity (0-31)
  10-14 Blue Intensity  (0-31)
*/

void LCD::drawWindow(std::array<uint16_t, 38400>& pixelBuffer) {

    for(int i = 0; i < (pixelBuffer.size() * 4); i += 4) {
        uint16_t val = pixelBuffer[i >> 2];
        sf::Color colour = sf::Color((val & 0x1f) << 3, /* R */
                                     (val & 0x3E0) >> 2,  /* G */
                                     (val & 0x7C00) >> 7, 255); /* B */
        pixels[i].color = colour;
        pixels[i + 1].color = colour;
        pixels[i + 2].color = colour;
        pixels[i + 3].color = colour;
    }    

    if(gbaWindow->isOpen()) {
        while(gbaWindow->pollEvent(event)) {
            if(event.type == sf::Event::Closed) {
                gbaWindow->close();
                exit(0);
            }
            if(event.type == sf::Event::Resized) {
                sf::FloatRect visibleArea(0, 0, event.size.width, event.size.height);
                sf::View view = sf::View(visibleArea);
                gbaWindow->setView(view);
                changeResolution(pixels, (float)event.size.width, (float)event.size.height);
            }
        }
        gbaWindow->clear(sf::Color::Black);
        gbaWindow->draw(pixels);
        
        gbaWindow->display();
    }
    
}


void LCD::closeWindow() {
    gbaWindow->close();
}



<|start_filename|>src/PPU.cpp<|end_filename|>
#include "PPU.h"
#include "memory/Bus.h"
#include <SFML/Graphics.hpp>
#include <utility>
#include <algorithm>
#include <string>
#include <iostream>
#include "util/macros.h"
#include "assert.h"
#include <cmath>


PPU::PPU() {

}

PPU::~PPU() {

}

void PPU::renderScanline(uint16_t scanline) {
    // TODO: explain this logic in a comment
    if(scanline > (SCREEN_HEIGHT - 2) && scanline < 226) {
        return;
    }
    
    if(scanline > (SCREEN_HEIGHT - 1)) {
        // for special case where scanline == 226
        scanlineBackDropColours[0] = getBackdropColour();
    } else {
        scanlineBackDropColours[(scanline + 1) % 228] = getBackdropColour();
    }

    uint8_t bgMode = (bus->iORegisters[Bus::DISPCNT] & 0x7);

    switch(bgMode) {
        case 0: {
            // render spritebuffer for [scanline+2, 159] 
            renderSprites((scanline + 2) % 228);
            // render bgbuffer for [scanline+1, 159]  
            renderBg((scanline + 1) % 228);
            break;
        }
        case 1: {
            DEBUGWARN("in bg mode 1 unimplemented\n");
            break;
        }
        case 2: {
            DEBUGWARN("in bg mode 2 unimplemented\n");
            break;
        }
        /*
            mode	width	height	bpp	size	    page-flip
            3  	    240	    160	    16	1x12C00h	No
            4 	    240	    160	    8	2x9600h	    Yes
            5 	    160	    128	    16	2xA000h	    Yes
        */
        case 3: {
            // simple bitmap mode
            for(int x = 0; x < SCREEN_WIDTH; x++) {
                bgBuffer[scanline * SCREEN_WIDTH + x] = (bus->vRam[((scanline * SCREEN_WIDTH + x) << 1) + 1] << 8) | 
                                                           (bus->vRam[(scanline * SCREEN_WIDTH + x) << 1]); 
            } 
            break;
        }
        case 4: {
            // page flipping mode
            renderSprites((scanline + 2) % 228);
            if(!(bus->iORegisters[Bus::IORegister::DISPCNT] & 0x10)) {
                // page 0
                for(int x = 0; x < SCREEN_WIDTH; x++) {
                    bgBuffer[scanline * SCREEN_WIDTH + x] = 
                        indexBgPalette8Bpp(bus->vRam[(scanline * SCREEN_WIDTH + x)]);
                } 
            } else {
                // page 1
                for(int x = 0; x < SCREEN_WIDTH; x++) {
                    bgBuffer[scanline * SCREEN_WIDTH + x] = 
                        indexBgPalette8Bpp(bus->vRam[(scanline * SCREEN_WIDTH + x + 0xA000)]);
                }   
            }

            break;
        }
        case 5: {
            // page flipping mode
            if(!(bus->iORegisters[Bus::IORegister::DISPCNT] & 0x10)) {
                // page 0
                for(int x = 0; x < SCREEN_WIDTH; x++) {
                    if(scanline >= 128 || x >= 160) {
                        bgBuffer[scanline * SCREEN_WIDTH + x] = indexBgPalette8Bpp(0);
                    } else {
                        bgBuffer[scanline * SCREEN_WIDTH + x] = (bus->vRam[((scanline * 160 + x) << 1) + 1] << 8) | 
                                                                    (bus->vRam[(scanline * 160 + x) << 1]); 
                    }
                } 
            } else {
                // page 1
                for(int x = 0; x < SCREEN_WIDTH; x++) {
                    if(scanline >= 128 || x >= 160) {
                        bgBuffer[scanline * SCREEN_WIDTH + x] = indexBgPalette8Bpp(0);
                    } else {
                        bgBuffer[scanline * SCREEN_WIDTH + x] = (bus->vRam[((scanline * 160 + x) << 1) + 1 + 0xA000] << 8) | 
                                                                    (bus->vRam[((scanline * 160 + x) << 1) + 0xA000]); 
                    }                
                }   
            }
            break;
        }
        default: {
            assert(false);
            break;
        }
    }
}

void PPU::connectBus(std::shared_ptr<Bus> _bus) {
    this->bus = _bus;
}


uint16_t PPU::getBackdropColour() {
    return ((uint16_t)bus->paletteRam[0 << 1]) |
           ((uint16_t)bus->paletteRam[(0 << 1) + 1] << 8);
}

uint32_t PPU::indexBgPalette8Bpp(uint8_t index) {
    if(!index) {
        return transparentColour;
    }
    return ((uint16_t)bus->paletteRam[index << 1]) |
           ((uint16_t)bus->paletteRam[(index << 1) + 1] << 8);
}

uint32_t PPU::indexBgPalette4Bpp(uint8_t index) {
    if(!(index & 0x0F)) {
        return transparentColour;
    }
    return ((uint16_t)bus->paletteRam[index << 1]) |
           ((uint16_t)bus->paletteRam[(index << 1) + 1] << 8);
}


uint32_t PPU::indexObjPalette4Bpp(uint8_t index) {
    // TODO: this is just temporary
    if(!(index & 0x0F)) {
        return transparentColour;
    }
    return ((uint16_t)bus->paletteRam[(index << 1) + 0x200]) |
           ((uint16_t)bus->paletteRam[(index << 1) + 1 + 0x200] << 8);    
}

uint32_t PPU::indexObjPalette8Bpp(uint8_t index) {
    if(!index) {
        return transparentColour;
    }
    return ((uint16_t)bus->paletteRam[(index << 1) + 0x200]) |
           ((uint16_t)bus->paletteRam[(index << 1) + 1 + 0x200] << 8);    
}

void PPU::setObjectsDirty() {
    dirty = true;
}

/*
    06010000-06017FFF  32 KBytes OBJ Tiles
*/
// TODO: Maximum Number of Sprites per Line
void PPU::renderSprites(uint16_t scanline) {
    if(scanline > SCREEN_HEIGHT - 1) {
        return;
    }

    if(bus->iORegisters[Bus::IORegister::DISPCNT + 1] & 0x80) {
        // SPRITE WINDOW SPRITE WINDOW SPRITE WINDOW!!!!!
        scanlineObjectWindowData[scanline] = bus->iORegisters[Bus::IORegister::WINOUT + 1] & 0x3F;
    }

    //spriteBuffer.fill(transparentColour);
    // mapping mode 1 =  1d mapping, 0 = 2d mapping
    bool oneDimMapping = bus->iORegisters[Bus::IORegister::DISPCNT] & 0x40;
    // interate through all OAM attributes (object), from lowest priority to highest
    // TODO: get rid of the magic numbers
    for(int32_t address = 0x3F8; address >= 0x0; address -= 0x8) {
        uint16_t objAttr0 = bus->objAttributes[address] | (bus->objAttributes[address + 1] << 8);
        uint16_t objAttr1 = bus->objAttributes[address + 2] | (bus->objAttributes[address + 2 + 1] << 8);
        uint16_t objAttr2 = bus->objAttributes[address + 4] | (bus->objAttributes[address + 4 + 1] << 8);

        bool colorMode = objAttr0 & 0x2000; // 16 colors (4bpp) if cleared; 256 colors (8bpp) if set.

        uint8_t objMode = (objAttr0 & 0x0300) >> 8;
        if(objMode == 2) {
            // do not render this sprite
            continue;
        }

        // to be included in spriteBuffer pixel bits 16-17 for render calclation at frame draw time
        uint32_t drawMode = (uint32_t)(objAttr0 & 0x0C00) << 6;

        uint32_t base = (objAttr2 & 0x03FF);

        uint8_t paletteBank = (objAttr2 & 0xF000) >> 8;
        uint8_t priority = (objAttr2 & 0x0C00) >> 10;

        // [shape][size]
        Dimension dim = spriteDimensions[(objAttr0 & 0xC000) >> 14][(objAttr1 & 0xC000) >> 14];
        int32_t width = dim.width * 8;
        int32_t height = dim.height * 8;

        // implementation detail
        uint32_t priorityOffset = SCREEN_WIDTH * SCREEN_HEIGHT * priority;
        int32_t screenXOffset = objAttr1 & 0x01FF;
        int32_t screenYOffset = objAttr0 & 0x00FF;

        int16_t pa = 0x100;
        int16_t pb = 0;
        int16_t pc = 0;
        int16_t pd = 0x100;
        int32_t boundingWidth = width;
        int32_t boundingHeight = height;
        bool hFlip = false;
        bool vFlip = false;

        if(objAttr0 & 0x100) {
            // rotation / scaling flag (affine sprites enabled)
            if(objAttr0 & 0x200) {
                boundingWidth *= 2;
                boundingHeight *= 2;
            } 
            uint32_t paramSelection = (objAttr1 & 0x3E00) >> 9;
            uint32_t paramBaseAddr = 0x6 + paramSelection * 32;
            pa = bus->objAttributes[paramBaseAddr + 0 * 8] | (bus->objAttributes[paramBaseAddr + 0 * 8 + 1] << 8);
            pb = bus->objAttributes[paramBaseAddr + 1 * 8] | (bus->objAttributes[paramBaseAddr + 1 * 8 + 1] << 8);
            pc = bus->objAttributes[paramBaseAddr + 2 * 8] | (bus->objAttributes[paramBaseAddr + 2 * 8 + 1] << 8);
            pd = bus->objAttributes[paramBaseAddr + 3 * 8] | (bus->objAttributes[paramBaseAddr + 3 * 8 + 1] << 8);

        } else {
            if(objAttr0 & 0x200) {
                // obj disabled
                continue;
            } 

            if(objAttr1 & 0x1000) {
                // horizontal flip
                hFlip = true;
            }
            if(objAttr1 & 0x2000) {
                // vertical flip
                vFlip = true;
            }
        }

        int32_t halfWidth = boundingWidth / 2;
        int32_t halfHeight = boundingHeight / 2;

        if(screenYOffset >= SCREEN_HEIGHT) {
            screenYOffset = screenYOffset - 255;
        }

        int32_t y = (int32_t)scanline - screenYOffset - halfHeight;

        int32_t screenY = scanline;
    
        if((y + halfHeight) < 0 || boundingHeight < (y + halfHeight)) {
            // sprite outside scanline, doesnt need to be rendered
            continue;
        }

        for(int32_t x = -halfWidth; x < halfWidth; x++) {
            
            int32_t screenX = x + screenXOffset + halfWidth;

            screenX &= 0x1FF;

            if(screenX >= SCREEN_WIDTH) {
                continue;
            }
    
            uint32_t colour;
            uint32_t tileAddress = 0;

            int32_t textureX = ((pa * x + pb * y) >> 8) + (width / 2);
            int32_t textureY = ((pc * x + pd * y) >> 8) + (height / 2);

            if(hFlip) {
                textureX = width - textureX - 1;
            }
            if(vFlip) {
                textureY = height - textureY - 1;
            }

            if(textureX < 0 || width <= textureX ||  
               textureY < 0 || height <= textureY) {
                continue;
            }

            if(colorMode) {

                // 8 bpp
                uint32_t tileNum = oneDimMapping ? base + ((textureY / 8) * dim.width + (textureX / 8)) * 2 :
                            base + ((textureY / 8) * 32 + (textureX / 8)) * 2;

                tileNum &= 0x3FF;
                tileAddress = 0x10000 + tileNum * 0x20; 
            } else {

                // 4 bpp
                uint32_t tileNum = oneDimMapping ? base + ((textureY / 8) * dim.width + (textureX / 8)):
                            base + ((textureY / 8) * 32 + (textureX / 8));

                tileNum &= 0x3FF;
                tileAddress = 0x10000 + tileNum * 0x20; 
            }    


            if(colorMode) {
                colour = indexObjPalette8Bpp(bus->vRam[tileAddress + (textureY % 8) * 8 + (textureX % 8)]);
            } else {
                if(textureX % 2) {
                    colour = indexObjPalette4Bpp(((bus->vRam[tileAddress + (((textureY % 8) * 8 + (textureX % 8)) >> 1)] & 0xF0) >> 4) | paletteBank); 
                } else {
                    colour = indexObjPalette4Bpp((bus->vRam[tileAddress + (((textureY % 8) * 8 + (textureX % 8)) >> 1)] & 0xF) | paletteBank);
                }
            }

            if(colour != transparentColour) {
                spriteBuffer[priorityOffset + screenY * SCREEN_WIDTH + screenX] = colour | drawMode;
            }
        
        }
    }

}


void PPU::renderBg(uint16_t scanline) {
    if(scanline > (SCREEN_HEIGHT - 1)) {
        scanline = 0;
    }

    if(bus->iORegisters[Bus::IORegister::DISPCNT + 1] & 0xE0) {
        // WINDOWING WINDOWING WINDOWING
        if(bus->iORegisters[Bus::IORegister::DISPCNT + 1] & 0x20) {
            // window 0
            scanlineBgWindowData[scanline].enabled = true;
            scanlineBgWindowData[scanline].bottom = bus->iORegisters[Bus::IORegister::WIN0V];
            scanlineBgWindowData[scanline].top = (bus->iORegisters[Bus::IORegister::WIN0V + 1]);
            scanlineBgWindowData[scanline].right = bus->iORegisters[Bus::IORegister::WIN0H];
            scanlineBgWindowData[scanline].left = bus->iORegisters[Bus::IORegister::WIN0H + 1];
            scanlineBgWindowData[scanline].metaData = bus->iORegisters[Bus::IORegister::WININ] & 0x3F;
        }
        if(bus->iORegisters[Bus::IORegister::DISPCNT + 1] & 0x40) {
            // window 1
            scanlineBgWindowData[SCREEN_HEIGHT + scanline].enabled = true;
            scanlineBgWindowData[SCREEN_HEIGHT + scanline].bottom = bus->iORegisters[Bus::IORegister::WIN1V];
            scanlineBgWindowData[SCREEN_HEIGHT + scanline].top = (bus->iORegisters[Bus::IORegister::WIN1V + 1]);
            scanlineBgWindowData[SCREEN_HEIGHT + scanline].right = bus->iORegisters[Bus::IORegister::WIN1H];
            scanlineBgWindowData[SCREEN_HEIGHT + scanline].left = bus->iORegisters[Bus::IORegister::WIN1H + 1];
            scanlineBgWindowData[SCREEN_HEIGHT + scanline].metaData = bus->iORegisters[Bus::IORegister::WININ + 1] & 0x3F;
        }  
        scanlineOutsideWindowData[scanline] = bus->iORegisters[Bus::IORegister::WINOUT] & 0x3F;
    }

    renderBgX(scanline, 0);
    renderBgX(scanline, 1);
    renderBgX(scanline, 2);
    renderBgX(scanline, 3);
}


/*
  BGXCNT
  Bit   Expl.
  0-1   BG Priority           (0-3, 0=Highest)
  2-3   Character Base Block  (0-3, in units of 16 KBytes) (=BG Tile Data)
  4-5   Not used (must be zero) (except in NDS mode: MSBs of char base)
  6     Mosaic                (0=Disable, 1=Enable)
  7     Colors/Palettes       (0=16/16, 1=256/1)
  8-12  Screen Base Block     (0-31, in units of 2 KBytes) (=BG Map Data)
  13    BG0/BG1: Not used (except in NDS mode: Ext Palette Slot for BG0/BG1)
  13    BG2/BG3: Display Area Overflow (0=Transparent, 1=Wraparound)
  14-15 Screen Size (0-3)
*/
void PPU::renderBgX(uint16_t scanline, uint8_t x) {
    if(!(bus->iORegisters[Bus::IORegister::DISPCNT + 1] & (1 << x))) {
        // check if x screen enabled
        return;
    }

    uint16_t bgCnt = bus->iORegisters[0x8 + x * 2] | (bus->iORegisters[0x8 + x * 2 + 1] << 8);
    uint8_t priority = bgCnt & 0x3;
    uint32_t bgBufferOffset = x * SCREEN_HEIGHT * SCREEN_WIDTH;
    uint8_t tileBaseBlock = (bgCnt & 0xC) >> 2;
    uint8_t screenBaseBlock = (bgCnt & 0x1F00) >> 8;
    uint8_t size = (bgCnt & 0xC000) >> 14;
    Dimension bgDim = textBgDimensions[size];
    uint8_t width = bgDim.width;
    uint8_t height = bgDim.height; 

    uint16_t hOffset =  (bus->iORegisters[0x10 + x * 4] | 
                        (bus->iORegisters[0x10 + x * 4 + 1] << 8)) & 0x1FF;
    uint16_t vOffset =  (bus->iORegisters[0x12 + x * 4] | 
                        (bus->iORegisters[0x12 + x * 4 + 1] << 8)) & 0x1FF;

    uint32_t screenX = 0;
    uint32_t screenY = 0;

    bool colorMode = bgCnt & 0x0080;

    uint32_t tileWidth = colorMode ? 64 : 32;

    int16_t pa = 1;
    int16_t pb = 0;
    int16_t pc = 1;
    int16_t pd = 0;

    for(uint8_t x = 0; x < width; x++) {
        uint8_t screenBlockX = x / 32;
        for(uint8_t y = 0; y < height; y++) {
            /*
                In 'Text Modes', the screen size is organized as follows: 
                The screen consists of one or more 256x256 pixel (32x32 tiles) areas. 
                When Size=0: only 1 area (SC0), 
                when Size=1 or Size=2: two areas (SC0,SC1 either horizontally or vertically arranged next to each other), 
                when Size=3: four areas (SC0,SC1 in upper row, SC2,SC3 in lower row). 
                Whereas SC0 is defined by the normal BG Map base address 
                (Bit 8-12 of BGxCNT), SC1 uses same address +2K, SC2 address +4K, SC3 address +6K. 
                When the screen is scrolled it'll always wraparound.
            */
            uint8_t screenBlockY = y / 32;
            uint8_t screenBlock = (screenBlockX + screenBlockY * (width / 32));

            uint32_t addr = (((y % 32) * 32) + (x % 32)) * 2 + (screenBaseBlock + screenBlock) * 0x800;
            
            uint16_t screenEntry = ((uint16_t)bus->vRam[addr]) | 
                                  ((uint16_t)(bus->vRam[addr + 1] << 8));


            int8_t hFlipMultiplier = 1;
            int8_t hFlipOffset = 0;
            int8_t vFlipMultiplier = 1;
            int8_t vFlipOffset = 0;

            if(screenEntry & 0x0400) {
                // hFlip
                hFlipMultiplier = -1;
                hFlipOffset = 7;
            }
            if(screenEntry & 0x0800) {
                // vFlip
                vFlipMultiplier = -1;
                vFlipOffset = 7;
            }

            // first check if this tile will be rendered
            screenY = ((uint32_t)(y * 8 + (vFlipOffset + vFlipMultiplier * 0)) - vOffset) % ((uint32_t)height * 8);
            if((screenY + 7) < scanline) {continue;}

            uint8_t paletteBank = (screenEntry & 0xF000) >> 8;

            uint32_t tileIndex = screenEntry & 0x3FF;
            uint32_t offset = ((uint32_t)tileBaseBlock) * 0x4000 + tileIndex * tileWidth; // TODO: or 64 if in 8bpp mode

            for(uint8_t tileY = 0; tileY < 8; tileY++) {
                screenY = (((uint32_t)y * 8 + (vFlipOffset + vFlipMultiplier * tileY)) - vOffset) % ((uint32_t)height * 8);

                if(screenY != scanline) {
                    continue;
                }
                for(uint8_t tileX = 0; tileX < 8; tileX++) {
                    screenX = (((uint32_t)x * 8 + (hFlipOffset + hFlipMultiplier * tileX)) - hOffset) % ((uint32_t)width * 8);
                    
                    if(screenX >= SCREEN_WIDTH || screenY >= SCREEN_HEIGHT) {
                        continue;
                    }
                    
                    if(colorMode) {
                        bgBuffer[bgBufferOffset + screenY * SCREEN_WIDTH + screenX] = 
                            indexBgPalette8Bpp(bus->vRam[offset + tileY * 8 + tileX]) | ((uint32_t)priority << 16);
                   } else {
                        if(tileX % 2) {
                            // tile x odd
                            // TODO: clean this up...
                            bgBuffer[bgBufferOffset + screenY * SCREEN_WIDTH + screenX] = 
                                indexBgPalette4Bpp(((bus->vRam[offset + ((tileY * 8 + tileX) / 2)] & 0xF0) >> 4) | paletteBank) | 
                                ((uint32_t)priority << 16);
                        } else {
                            // tile x even
                            bgBuffer[bgBufferOffset + screenY * SCREEN_WIDTH + screenX] = 
                                indexBgPalette4Bpp((bus->vRam[offset + ((tileY * 8 + tileX) / 2)] & 0xF) | paletteBank) |
                                ((uint32_t)priority << 16);
                        }                    
                    }

                }
                if(screenY == scanline) break;
            }
            if(screenY == scanline) break;           
        } 
    } 
}

bool PPU::isTransparent(uint32_t pixelData) {
    return pixelData & transparentColour;
}

PPU::Coords PPU::convertScreenCoordsToSpriteCoords(int32_t screenX, int32_t screenY, int16_t pa, int16_t pb, int16_t pc, int16_t pd, int32_t xRotCentre, int32_t yRotCentre) {

    // float paScale = (float)(pa) / 256.0;
    // float pbScale = (float)(pb) / 256.0;
    // float pcScale = (float)(pc) / 256.0;
    // float pdScale = (float)(pd) / 256.0;

    // uint32_t spriteX = ((paScale * (float)((int32_t)screenX - (int32_t)xRotCentre) + pbScale * (float)((int32_t)screenY - (int32_t)yRotCentre) + (float)(xRotCentre)));
    // uint32_t spriteY = ((pcScale * (float)((int32_t)screenX - (int32_t)xRotCentre) + pdScale * (float)((int32_t)screenY - (int32_t)yRotCentre) + (float)(yRotCentre)));
    int32_t spriteX = ((pa * (int)screenX + pb * (int)screenY) >> 8) + xRotCentre;
    int32_t spriteY = ((pc * (int)screenX + pd * (int)screenY) >> 8) + yRotCentre;
    // uint32_t spriteX = (paScale * (float)((int32_t)screenX ) + pbScale * (float)((int32_t)screenY) );
    // uint32_t spriteY = (pcScale * (float)((int32_t)screenX ) + pdScale * (float)((int32_t)screenY) );

    DEBUGWARN(spriteX << " spriteX\n");
    DEBUGWARN(spriteX << " spriteY\n");

    return {spriteX, spriteY};
}


// this is only called once per frame
std::array<uint16_t, PPU::SCREEN_WIDTH * PPU::SCREEN_HEIGHT>& PPU::renderCurrentScreen() {
    pixelBuffer.fill(0);
    // get the priorities of the backgrounds
    std::vector<std::pair<uint8_t, uint8_t>> bgPriorities;
    bgPriorities.push_back({(bgBuffer[0 * SCREEN_WIDTH * SCREEN_HEIGHT] & 0x30000) >> 16, 0});
    bgPriorities.push_back({(bgBuffer[1 * SCREEN_WIDTH * SCREEN_HEIGHT] & 0x30000) >> 16, 1});
    bgPriorities.push_back({(bgBuffer[2 * SCREEN_WIDTH * SCREEN_HEIGHT] & 0x30000) >> 16, 2});
    bgPriorities.push_back({(bgBuffer[3 * SCREEN_WIDTH * SCREEN_HEIGHT] & 0x30000) >> 16, 3});
    std::sort(bgPriorities.begin(), bgPriorities.end());
    // because going from lowest (3) to highest (0) prioirty
    // In case that the 'Priority relative to BG' is the same than the priority of one of the background layers, 
    // then the OBJ becomes higher priority and is displayed on top of that BG layer.   

    bool win0Enabled = false;
    bool win1Enabled = false;


    for(int y = 0; y < SCREEN_HEIGHT; y++) {
        uint8_t windowBgMask;
        uint8_t window0Left = 0;
        uint8_t window0Right = 0;
        uint8_t window1Left = 0;
        uint8_t window1Right = 0;
        bool window0 = false;
        bool window1 = false;
        bool windowed = false;
        if(scanlineBgWindowData[y].enabled) {
            windowed = true;
            // WINDOW 0
            if((scanlineBgWindowData[y].top <= y && y < scanlineBgWindowData[y].bottom) || 
               ((int8_t)scanlineBgWindowData[y].top <= y && y < (int8_t)scanlineBgWindowData[y].bottom)) {
                window0 = true;
                window0Left = scanlineBgWindowData[y].left;
                window0Right = scanlineBgWindowData[y].right;
            }
        }
        if(scanlineBgWindowData[SCREEN_HEIGHT + y].enabled) {
            windowed = true;
            // WINDOW 1
            if((scanlineBgWindowData[SCREEN_HEIGHT + y].top <= y && y < scanlineBgWindowData[SCREEN_HEIGHT + y].bottom) || 
               ((int8_t)scanlineBgWindowData[SCREEN_HEIGHT + y].top <= y && y < (int8_t)scanlineBgWindowData[SCREEN_HEIGHT + y].bottom)) {
                window1 = true;
                window1Left = scanlineBgWindowData[SCREEN_HEIGHT + y].left;
                window1Right = scanlineBgWindowData[SCREEN_HEIGHT + y].right;
            }         
        }

        for(int x = 0; x < SCREEN_WIDTH; x++) {
            pixelBuffer[y * SCREEN_WIDTH + x] = scanlineBackDropColours[y];

            for(int priority = 3; priority >= 0; priority--) {
                uint32_t bgOffset = (bgPriorities[priority].second) * SCREEN_HEIGHT * SCREEN_WIDTH;
                uint32_t bgPixel = bgBuffer[bgOffset + y * SCREEN_WIDTH + x];
                int spriteRelativePrio = bgPriorities[priority].first;

                if(windowed) {
                    if(window0 && 
                       ((window0Left <= x && x < window0Right) || 
                        ((int8_t)window0Left <= (int8_t)x && (int8_t)x < (int8_t)window0Right))) {
                        windowBgMask = scanlineBgWindowData[y].metaData; 
                    }   
                    else if(window1 &&                        
                           ((window1Left <= x && x < window1Right) || 
                           ((int8_t)window1Left <= (int8_t)x && (int8_t)x < (int8_t)window1Right))) {
                        windowBgMask = scanlineBgWindowData[SCREEN_HEIGHT + y].metaData; 
                    }
                    else {
                        windowBgMask = scanlineOutsideWindowData[y]; 
                    }
                    if((windowBgMask & (1 << (bgPriorities[priority].second)))) {
                        if(!isTransparent(bgPixel)) {
                            pixelBuffer[y * SCREEN_WIDTH + x] = bgPixel & 0xFFFF;
                        }                        
                    } 
                    if(windowBgMask & 0x10) {
                        // obj enable
                        for(int spritePrio = spriteRelativePrio; spritePrio >= 0; spritePrio--) {
                            uint32_t spriteOffset = spritePrio * SCREEN_HEIGHT * SCREEN_WIDTH;
                            uint32_t spritePixel = spriteBuffer[spriteOffset + y * SCREEN_WIDTH + x];
                            if(!isTransparent(spritePixel)) {
                                pixelBuffer[y * SCREEN_WIDTH + x] = spritePixel & 0xFFFF;
                        
                            }
                        }
                    }
                    // TODO: sprite window

                } else {
                    if(!isTransparent(bgPixel)) {
                        pixelBuffer[y * SCREEN_WIDTH + x] = bgPixel & 0xFFFF;
                    } 
                    for(int spritePrio = spriteRelativePrio; spritePrio >= 0; spritePrio--) {
                        uint32_t spriteOffset = spritePrio * SCREEN_HEIGHT * SCREEN_WIDTH;
                        uint32_t spritePixel = spriteBuffer[spriteOffset + y * SCREEN_WIDTH + x];
                        if(!isTransparent(spritePixel)) {
                            pixelBuffer[y * SCREEN_WIDTH + x] = spritePixel & 0xFFFF;        
                        }
                    }
                }

            }

        }
    }
    bgBuffer.fill(transparentColour | lowestPrio);
    spriteBuffer.fill(transparentColour);
    for(auto& windowData : scanlineBgWindowData) {
        windowData.enabled = false;
    }

    return pixelBuffer;
}




<|start_filename|>src/DMA.h<|end_filename|>
#include <cstdint>
#include <memory>

class Bus;
class ARM7TDMI;
class Scheduler;

class DMA {

    public:
        void connectBus(std::shared_ptr<Bus> bus);
        void connectCpu(std::shared_ptr<ARM7TDMI> cpu);
        void connectScheduler(std::shared_ptr<Scheduler> scheduler);

        uint32_t dmaX(uint8_t x, bool vBlank, bool hBlank, uint16_t scanline);

        void updateDmaUponWrite(uint32_t address, uint32_t value, uint8_t width);
        bool eepromBusWidthDetected = true;

    private:
        std::shared_ptr<Bus> bus;
        std::shared_ptr<ARM7TDMI> cpu;
        std::shared_ptr<Scheduler> scheduler;

        void scheduleDmaX(uint32_t x, uint8_t upperControlByte, bool immediately);

        static const uint32_t internalMemMask = 0x07FFFFFF;
        static const uint32_t anyMemMask      = 0x0FFFFFFF;
        static const uint32_t dma3MaxWordCount = 0x10000;
        static const uint32_t dma012MaxWordCount = 0x4000;
        static const uint32_t dma3WordMask = 0xFFFF;
        static const uint32_t dma012WordMask = 0x3FFF;

        bool dmaXEnabled[4] = {false, false, false, false};

        uint32_t dmaXSourceAddr[4] = {0, 0, 0, 0};
        uint32_t dmaXDestAddr[4] = {0, 0, 0, 0};
        uint32_t dmaXWordCount[4] = {0, 0, 0, 0};

        bool inVideoCaptureMode = false;

};

<|start_filename|>src/Debugger.cpp<|end_filename|>
#include "Debugger.h"
#include "arm7tdmi/ARM7TDMI.h"
#include "memory/Bus.h"


#define WORD_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c"
#define WORD_TO_BINARY(word)  \
  (word & 0x80000000 ? '1' : '0'), \
  (word & 0x40000000 ? '1' : '0'), \
  (word & 0x20000000 ? '1' : '0'), \
  (word & 0x10000000 ? '1' : '0'), \
  (word & 0x08000000 ? '1' : '0'), \
  (word & 0x04000000 ? '1' : '0'), \
  (word & 0x02000000 ? '1' : '0'), \
  (word & 0x01000000 ? '1' : '0'), \
  (word & 0x00800000 ? '1' : '0'), \
  (word & 0x00400000 ? '1' : '0'), \
  (word & 0x00200000 ? '1' : '0'), \
  (word & 0x00100000 ? '1' : '0'), \
  (word & 0x00080000 ? '1' : '0'), \
  (word & 0x00040000 ? '1' : '0'), \
  (word & 0x00020000 ? '1' : '0'), \
  (word & 0x00010000 ? '1' : '0'), \
  (word & 0x00008000 ? '1' : '0'), \
  (word & 0x00004000 ? '1' : '0'), \
  (word & 0x00002000 ? '1' : '0'), \
  (word & 0x00001000 ? '1' : '0'), \
  (word & 0x00000800 ? '1' : '0'), \
  (word & 0x00000400 ? '1' : '0'), \
  (word & 0x00000200 ? '1' : '0'), \
  (word & 0x00000100 ? '1' : '0'), \
  (word & 0x00000080 ? '1' : '0'), \
  (word & 0x00000040 ? '1' : '0'), \
  (word & 0x00000020 ? '1' : '0'), \
  (word & 0x00000010 ? '1' : '0'), \
  (word & 0x00000008 ? '1' : '0'), \
  (word & 0x00000004 ? '1' : '0'), \
  (word & 0x00000002 ? '1' : '0'), \
  (word & 0x00000001 ? '1' : '0')

Debugger::Debugger() {
    stepMode = false;
    if (cs_open(CS_ARCH_ARM, CS_MODE_ARM, &sCapstone) != CS_ERR_OK) {
        puts("cs_open failed");
        exit(1);
    }
    cs_option(sCapstone, CS_OPT_DETAIL, CS_OPT_ON);
}


void Debugger::step(ARM7TDMI* cpu, Bus* bus) {
    if(stepMode) {
        updateState(cpu, bus);
    } else if(cpu->currInstrAddress == breakpoint) {
        printf("Hit breakpoint, press LShift to step though instructions\n");
        stepMode = true;
        updateState(cpu, bus);
    } else {  
    }
}


void Debugger::printState() {
    printf(
        "\
**********************************CPU STATE***********************************\n\
r0:     0x%08X      r1:     0x%08X      r2:     0x%08X      r3:     0x%08X    \n\
r4:     0x%08X      r5:     0x%08X      r6:     0x%08X      r7:     0x%08X    \n\
r8:     0x%08X      r9:     0x%08X      r10:    0x%08X      r11:    0x%08X    \n\
r12:    0x%08X      r13(SP):0x%08X      r14(LR):0x%08X      r15(PC):0x%08X    \n\
\n\
r13irq: 0x%08X      r14irq: 0x%08X\n\
r13svc: 0x%08X      r14svc: 0x%08X\n\
r13und: 0x%08X      r14und: 0x%08X\n\
\n\
                                                  WATCH MEMORY:\n\
flags:      NZCV------------------EAIFTMODE       [0x%08X](32):   0x%08X\n\
cpsr:       " WORD_TO_BINARY_PATTERN "      [0x%08X](32):   0x%08X\n\
spsr_svc:   " WORD_TO_BINARY_PATTERN "      [0x%08X](32):   0x%08X\n\
spsr_fiq:   " WORD_TO_BINARY_PATTERN "      [0x%08X](32):   0x%08X\n\
spsr_abt:   " WORD_TO_BINARY_PATTERN "      [0x%08X](32):   0x%08X\n\
spsr_irq:   " WORD_TO_BINARY_PATTERN "      [0x%08X](32):   0x%08X\n\
spsr_und:   " WORD_TO_BINARY_PATTERN "\n\
\n\
[0x%08X]: %s    (0x%08X)\n\
******************************************************************************\n",
      r0, r1, r2, r3, 
      r4, r5, r6, r7,
      r8, r9, r10,r11,
      r12,r13,r14,r15,
      r13irq,r14irq,
      r13svc,r14svc,
      r13und,r14und,
      watchAddr1,watchMem1, 
      WORD_TO_BINARY(cpsr),
      watchAddr2,watchMem2,
      WORD_TO_BINARY(spsr_svc), 
      watchAddr3,watchMem3,
      WORD_TO_BINARY(spsr_fiq),
      watchAddr4,watchMem4,
      WORD_TO_BINARY(spsr_abt),
      watchAddr5,watchMem5,
      WORD_TO_BINARY(spsr_irq),
      watchAddr6,watchMem6,
      WORD_TO_BINARY(spsr_und), 
      instrAddress, instr.c_str(), instrWord);
}



void Debugger::updateState(ARM7TDMI* cpu, Bus* bus) {
    r0 = cpu->getRegister(0);
    r1 = cpu->getRegister(1);
    r2 = cpu->getRegister(2);
    r3 = cpu->getRegister(3);
    r4 = cpu->getRegister(4);
    r5 = cpu->getRegister(5);
    r6 = cpu->getRegister(6);
    r7 = cpu->getRegister(7);
    r8 = cpu->getRegister(8);
    r9 = cpu->getRegister(9);
    r10= cpu->getRegister(10);
    r11= cpu->getRegister(11);
    r12= cpu->getRegister(12);
    r13= cpu->getRegister(13);
    r14= cpu->getRegister(14);
    r15= cpu->getRegister(15);
    r13irq = cpu->r13_irq;
    r14irq = cpu->r14_irq;
    r13svc = cpu->r13_svc;
    r14svc = cpu->r14_svc;
    r13und = cpu->r13_und;
    r14und = cpu->r14_und;
    cpsr = ARM7TDMI::psrToInt(cpu->cpsr);
    spsr_abt = ARM7TDMI::psrToInt(cpu->SPSR_abt);
    spsr_svc = ARM7TDMI::psrToInt(cpu->SPSR_svc);
    spsr_irq = ARM7TDMI::psrToInt(cpu->SPSR_irq);
    spsr_fiq = ARM7TDMI::psrToInt(cpu->SPSR_fiq);
    spsr_und = ARM7TDMI::psrToInt(cpu->SPSR_und);

    watchMem1 = bus->view32(watchAddr1);
    watchMem2 = bus->view32(watchAddr2);
    watchMem3 = bus->view32(watchAddr3);
    watchMem4 = bus->view32(watchAddr4);
    watchMem5 = bus->view32(watchAddr5);
    watchMem6 = bus->view32(watchAddr6);

    instrAddress = cpu->currInstrAddress;
    instr = disassembleArm(cpu->currInstruction);
    instrWord = cpu->currInstruction;
}

static uint32_t signExtend23Bit(uint32_t value) {
    return (value & 0x00400000) ? (value | 0xFF800000) : value;
}

std::string Debugger::disassembleArm(uint32_t instruction) {
    struct cs_insn *insn;
    char buffer[50];
    bool thumbMode = cpsr & 0x20;
    cs_option(sCapstone, CS_OPT_MODE, ((thumbMode) ? CS_MODE_THUMB : CS_MODE_ARM) | CS_MODE_LITTLE_ENDIAN);
    uint32_t count;
    if(thumbMode) {
        // in THUMB MODE
        uint8_t instr[2];
        instr[0] = (instruction) & 0xFF;
        instr[1] = (instruction >> 8) & 0xFF;
        count = cs_disasm(sCapstone, instr, 2, 0, 1, &insn);
    } else {
        uint8_t instr[4];
        instr[0] = (instruction) & 0xFF;
        instr[1] = (instruction >> 8) & 0xFF;
        instr[2] = (instruction >> 16) & 0xFF;
        instr[3] = (instruction >> 24) & 0xFF;
        count = cs_disasm(sCapstone, instr, 4, 0, 1, &insn);
    }  
    if(count == 1) {
        sprintf(buffer, "%s %s", insn[0].mnemonic, insn[0].op_str); 
    } else {
        if((instruction & 0xF000) == 0xF000 && thumbMode) {
            // bl instruction isnt disassembled by capstone...
            bool lo = instruction & 0x0800;
            uint32_t offset = lo ? ((instruction & 0x07FF)) << 1 : signExtend23Bit((instruction & 0x07FF) << 12);

            sprintf(buffer, "%s #0x%08X", (lo ? "bl lo" : "bl hi"), offset);
        } else {
            DEBUGWARN("capstone disassemble count != 1, instead is " << count << " \n");
        }

    }    
    cs_free(insn, count);

    return buffer;
}

bool Debugger::stepMode;

<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbLdStRegOffHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"


// FFC0
template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbLdStRegOffHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xF000) == 0x5000);
    assert(!(instruction & 0x0200));
    //uint8_t opcode = (instruction & 0x0C00) >> 10;
    constexpr uint8_t opcode = (op & 0x030) >> 4;
    uint8_t rd = thumbGetRd(instruction);
    uint8_t rb = thumbGetRb(instruction);
    //uint8_t ro = (instruction & 0x01C0) >> 6;
    constexpr uint8_t ro = (op & 0x007);
    uint32_t address = cpu->getRegister(rb) + cpu->getRegister(ro);

    if constexpr(opcode == 0) {
        // 0: STR  Rd,[Rb,Ro]   ;store 32bit data  WORD[Rb+Ro] = Rd
        cpu->bus->write32(address, cpu->getRegister(rd), Bus::CycleType::NONSEQUENTIAL);
    } else if constexpr(opcode == 1) {
        // 1: STRB Rd,[Rb,Ro]   ;store  8bit data  BYTE[Rb+Ro] = Rd
        cpu->bus->write8(address, (uint8_t)(cpu->getRegister(rd)), Bus::CycleType::NONSEQUENTIAL);
    } else if constexpr(opcode == 2) {
        // 2: LDR  Rd,[Rb,Ro]   ;load  32bit data  Rd = WORD[Rb+Ro]
        uint32_t value = aluShiftRor(cpu->bus->read32(address, Bus::CycleType::NONSEQUENTIAL),
                                        (address & 3) * 8);
        cpu->setRegister(rd, value);
        cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);        
    } else {
        // opcode == 3
        // 3: LDRB Rd,[Rb,Ro]   ;load   8bit data  Rd = BYTE[Rb+Ro]
        uint32_t value = (uint32_t)cpu->bus->read8(address, Bus::CycleType::NONSEQUENTIAL);
        cpu->setRegister(rd, value);
        cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
    }

    return NONSEQUENTIAL;
}


<|start_filename|>src/arm7tdmi/ARMInstructions/ArmBdtHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"
#include "assert.h"



template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::armBdtHandler(uint32_t instruction, ARM7TDMI* cpu) {
    // TODO: data aborts (if even applicable to GBA?)
    assert((instruction & 0x0E000000) == 0x08000000);
    // base register
    uint8_t rn = cpu->getRn(instruction);
    // align memory address;
    uint32_t rnVal = cpu->getRegister(rn);
    assert(rn != 15);

    constexpr bool p = op & 0x100;
    constexpr bool u = op & 0x080;
    constexpr bool l = op & 0x010;
    // special case for block transfer, s = what is usually b
    constexpr bool s = op & 0x040;
    bool w = dataTransGetW(instruction);

    if(!(instruction & 0x0000FFFF)) {
        // Empty Rlist: R15 loaded/stored (ARMv4 only), and Rb=Rb+/-40h (ARMv4-v5).
        instruction |= 0x00008000;
        // manually setting write bit to false so wont overwrite rn
        w = false;
        if constexpr(u) {
            cpu->setRegister(rn, rnVal + 0x40);
        } else {
            cpu->setRegister(rn, rnVal - 0x40);
        }
    }

    if constexpr (s) {
        assert(cpu->cpsr.Mode != USER);
    }
    uint16_t regList = (uint16_t)instruction;
    uint32_t addressRnStoredAt = 0;  // see below
    bool firstAccess = true;

    if constexpr(u) {
        // up, add offset to base
        if constexpr(p) {
            // pre-increment addressing
            rnVal += 4;
        }
        for (int reg = 0; reg < 16; reg++) {
            if (regList & 1) {
                if constexpr(l) {
                    // LDM{cond}{amod} Rn{!},<Rlist>{^}  ;Load  (Pop)
                    uint32_t data;
                    if(firstAccess) {
                        data = aluShiftRor(cpu->bus->read32(rnVal, Bus::CycleType::NONSEQUENTIAL), 
                                           (rnVal & 3) * 8);
                        firstAccess = false;
                    } else {
                        data = aluShiftRor(cpu->bus->read32(rnVal, Bus::CycleType::SEQUENTIAL), 
                                           (rnVal & 3) * 8);
                    }
                    if constexpr(!s) {
                        cpu->setRegister(reg, data);
                    } else {
                        cpu->setUserRegister(reg, data);
                    }
                    if(reg == rn) {
                        // when base is in rlist no writeback (LDM/ARMv4),
                        w = false;
                    }
                } else {
                    // STM{cond}{amod} Rn{!},<Rlist>{^}  ;Store (Push)
                    if (reg == rn) {
                        // hacky!
                        addressRnStoredAt = rnVal;
                    }
                    uint32_t data;
                    if constexpr(!s) {
                        data = cpu->getRegister(reg);
                    } else {
                        data = cpu->getUserRegister(reg);
                    }  
                    // TODO: take this out when implemeinting pipelining
                    if (reg == 15) {
                        data += 8;
                    }

                    if(firstAccess) {
                        cpu->bus->write32(rnVal, data, Bus::CycleType::NONSEQUENTIAL);
                        firstAccess = false;
                    } else {
                        cpu->bus->write32(rnVal, data, Bus::CycleType::SEQUENTIAL);
                    }
                }
                rnVal += 4;
            }
            regList >>= 1;
        }
        if constexpr(p) {
            // adjust the final rnVal so it is correct
            rnVal -= 4;
        }

    } else {
        // down, subtract offset from base
        if constexpr(p) {
            // pre-increment addressing
            rnVal -= 4;
        }
        for (int reg = 15; reg >= 0; reg--) {
            if (regList & 0x8000) {
                if constexpr(l) {
                    // LDM{cond}{amod} Rn{!},<Rlist>{^}  ;Load  (Pop)
                    uint32_t data;
                    if(firstAccess) {
                        data = aluShiftRor(cpu->bus->read32(rnVal, Bus::CycleType::NONSEQUENTIAL), 
                                           (rnVal & 3) * 8);
                        firstAccess = false;
                    } else {
                        data = aluShiftRor(cpu->bus->read32(rnVal, Bus::CycleType::SEQUENTIAL), 
                                           (rnVal & 3) * 8);           
                    }
                    if constexpr(!s) {
                        cpu->setRegister(reg, data);
                    } else {
                        cpu->setUserRegister(reg, data);
                    }                    
                    if(reg == rn) {
                        // when base is in rlist no writeback (LDM/ARMv4),
                        w = false;
                    }
                } else {
                    // STM{cond}{amod} Rn{!},<Rlist>{^}  ;Store (Push)
                    if (reg == rn) {
                        // hacky!
                        addressRnStoredAt = rnVal;
                    }

                    uint32_t data;
                    if constexpr(!s) {
                        data = cpu->getRegister(reg);
                    } else {
                        data = cpu->getUserRegister(reg);
                    } 
                    if (reg == 15) data += 8;
                    if(firstAccess) {
                        cpu->bus->write32(rnVal, data, Bus::CycleType::NONSEQUENTIAL);
                        firstAccess = false;
                    } else {
                        cpu->bus->write32(rnVal, data, Bus::CycleType::SEQUENTIAL);
                    }
                }
                rnVal -= 4;
            }
            regList <<= 1;
        }
        if constexpr(p) {
            // adjust the final rnVal so it is correct
            rnVal += 4;
        }
    }

    if(w) {
        if constexpr(!l) {
            if(((uint16_t)(instruction << (15 - rn)) > 0x8000)) {
                // check if base is not first reg to be stored
                // A STM which includes storing the base, with the base
                // as the first register to be stored, will therefore
                // store the unchanged value, whereas with the base second
                // or later in the transfer order, will store the modified value.
                assert(addressRnStoredAt != 0);
                // TODO: how to tell of sequential or nonsequential
                cpu->bus->write32(addressRnStoredAt, rnVal, Bus::CycleType::SEQUENTIAL);
            }
        }
        cpu->setRegister(rn, rnVal);
    }

    if constexpr(s && l) {
        if((instruction & 0x00008000)) {
            // f instruction is LDM and R15 is in the list: (Mode Changes)
            // While R15 loaded, additionally: CPSR=SPSR_<current mode>
            // TODO make sure to switch mode ANYWHERE where cpsr is set
            cpu->cpsr = *(cpu->getCurrentModeSpsr());
            cpu->switchToMode(ARM7TDMI::Mode(cpu->cpsr.Mode));
        }
    }

    cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
    if constexpr(l) {
        if((instruction & 0x00008000)) {
            // LDM with PC in list
            return BRANCH;
        } else {
            return NONSEQUENTIAL;
        }
    } else {
        return NONSEQUENTIAL;
    }



}

<|start_filename|>src/Gamepad.cpp<|end_filename|>
#include "memory/Bus.h"
#include "Gamepad.h"

void Gamepad::getInput(Bus* bus) {
    /*
        Bit   Expl.
        0     Button A        (0=Pressed, 1=Released)
        1     Button B        (etc.)
        2     Select          (etc.)
        3     Start           (etc.)
        4     Right           (etc.)
        5     Left            (etc.)
        6     Up              (etc.)
        7     Down            (etc.)
        8     Button R        (etc.)
        9     Button L        (etc.)
        10-15 Not used
    */
    uint8_t keysPressedByte1 = 0;
    uint8_t keysPressedByte2 = 0;

    keysPressedByte1 |= !sf::Keyboard::isKeyPressed(A);
    keysPressedByte1 |= (!sf::Keyboard::isKeyPressed(B) << 1);
    keysPressedByte1 |= (!sf::Keyboard::isKeyPressed(SELECT) << 2);
    keysPressedByte1 |= (!sf::Keyboard::isKeyPressed(START) << 3);
    keysPressedByte1 |= (!sf::Keyboard::isKeyPressed(DPAD_RIGHT) << 4);
    keysPressedByte1 |= (!sf::Keyboard::isKeyPressed(DPAD_LEFT) << 5);
    keysPressedByte1 |= (!sf::Keyboard::isKeyPressed(DPAD_UP) << 6);
    keysPressedByte1 |= (!sf::Keyboard::isKeyPressed(DPAD_DOWN) << 7);
    keysPressedByte2 |= (!sf::Keyboard::isKeyPressed(SHOULDER_RIGHT) << 0);
    keysPressedByte2 |= (!sf::Keyboard::isKeyPressed(SHOULDER_LEFT) << 1);

    bus->iORegisters[Bus::IORegister::KEYINPUT] = keysPressedByte1;
    bus->iORegisters[Bus::IORegister::KEYINPUT + 1] = keysPressedByte2;
}

<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbAddOffSpHandler.h<|end_filename|>
#pragma once

#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

// op = instruction & 0xFFC0;
// anything within the mask can be constexpr!

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbAddOffSpHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xFF00) == 0xB000);
    constexpr bool opcode = (op & 0x002);
    //uint8_t opcode = (instruction & 0x0080) >> 7;
    uint16_t offset = (instruction & 0x007F) << 2;

    if constexpr(!opcode) {
        // 0: ADD  SP,#nn       ;SP = SP + nn
        uint32_t value = cpu->getRegister(SP_REGISTER) + offset;
        cpu->setRegister(SP_REGISTER, value);
    } else {
        // 1: ADD  SP,#-nn      ;SP = SP - nn
        uint32_t value = cpu->getRegister(SP_REGISTER) - offset;
        cpu->setRegister(SP_REGISTER, value);
    }

    return SEQUENTIAL;
}


<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbLdStHwHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

// op = instruction & 0xFFC0;
// anything within the mask can be constexpr!

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbLdStHwHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xF000) == 0x8000);
    //uint8_t opcode = (instruction & 0x0800) >> 11;
    constexpr bool opcode = (op & 0x020);
    uint8_t rd = thumbGetRd(instruction);
    uint8_t rb = thumbGetRb(instruction);
    //uint32_t offset = (instruction & 0x07C0) >> 5;
    constexpr uint32_t offset = (op & 0x01F) << 1;
    uint32_t address = cpu->getRegister(rb) + offset;

    if constexpr(!opcode) {
        // 0: STRH Rd,[Rb,#nn]  ;store 16bit data   HALFWORD[Rb+nn] = Rd
        cpu->bus->write16(address,
                     (uint16_t)cpu->getRegister(rd), 
                     Bus::CycleType::NONSEQUENTIAL);
    } else {
        // 1: LDRH Rd,[Rb,#nn]  ;load  16bit data   Rd = HALFWORD[Rb+nn]
        uint32_t value = aluShiftRor(cpu->bus->read16(address, 
                                     Bus::CycleType::NONSEQUENTIAL),
                                     (address & 1) * 8);
        cpu->setRegister(rd, value);
        cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
    }

    return NONSEQUENTIAL;
}


<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbImmHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

// op = instruction & 0xFFC0;
// anything within the mask can be constexpr!

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbImmHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xE000) == 0x2000);    
    // uint8_t opcode = (instruction & 0x1800) >> 11;
    constexpr uint8_t opcode = (op & 0x060) >> 5;

    uint32_t offset = instruction & 0x00FF;
    //uint8_t rd = (instruction & 0x0700) >> 8;
    constexpr uint8_t rd = (op & 0x01C) >> 2;

    bool carryFlag;
    bool signFlag;
    bool overflowFlag;
    bool zeroFlag;

    if constexpr(opcode == 0) {
        // 00b: MOV{S} Rd,#nn      ;move     Rd   = #nn
        cpu->setRegister(rd, offset);
        signFlag = aluSetsSignBit(offset);
        zeroFlag = aluSetsZeroBit(offset);
        carryFlag = cpu->cpsr.C;
        overflowFlag = cpu->cpsr.V;
    } else if constexpr(opcode == 1) {
        // 01b: CMP{S} Rd,#nn      ;compare  Void = Rd - #nn
        uint32_t rdVal = cpu->getRegister(rd);
        uint32_t result = rdVal - offset;
        signFlag = aluSetsSignBit(result);
        zeroFlag = aluSetsZeroBit(result);
        carryFlag = aluSubtractSetsCarryBit(rdVal, offset);
        overflowFlag = aluSubtractSetsOverflowBit(rdVal, offset, result);
    } else if constexpr(opcode == 2) {
        // 10b: ADD{S} Rd,#nn      ;add      Rd   = Rd + #nn
        uint32_t rdVal = cpu->getRegister(rd);
        uint32_t result = rdVal + offset;
        signFlag = aluSetsSignBit(result);
        zeroFlag = aluSetsZeroBit(result);
        carryFlag = aluAddSetsCarryBit(rdVal, offset);
        overflowFlag = aluAddSetsOverflowBit(rdVal, offset, result);
        cpu->setRegister(rd, result);   
    } else {
        // opcode == 3
        // 11b: SUB{S} Rd,#nn      ;subtract Rd   = Rd - #nn
        uint32_t rdVal = cpu->getRegister(rd);
        uint32_t result = rdVal - offset;
        signFlag = aluSetsSignBit(result);
        zeroFlag = aluSetsZeroBit(result);
        carryFlag = aluSubtractSetsCarryBit(rdVal, offset);
        overflowFlag = aluSubtractSetsOverflowBit(rdVal, offset, result);
        cpu->setRegister(rd, result);
    }

    cpu->cpsr.C = carryFlag;
    cpu->cpsr.Z = zeroFlag;
    cpu->cpsr.N = signFlag;
    cpu->cpsr.V = overflowFlag;

    return SEQUENTIAL;
}


<|start_filename|>src/Timer.h<|end_filename|>
#include <cstdint>
#include <memory>

class Bus;
class ARM7TDMI;
class Scheduler;


class Timer {

    public: 
        void step(uint64_t cyclesElapsed);
        uint8_t updateBusToPrepareForTimerRead(uint32_t address, uint8_t width);
        void updateTimerUponWrite(uint32_t address, uint32_t value, uint8_t width);
        void connectBus(std::shared_ptr<Bus> bus);
        void connectCpu(std::shared_ptr<ARM7TDMI> cpu);
        void connectScheduler(std::shared_ptr<Scheduler> scheduler);
        void timerXOverflowEvent(uint8_t x);

        void updateTimer(uint32_t ioReg, uint8_t newValue);

    private:
        void stepTimerX(uint64_t cycles, uint8_t x);

        void setTimerXReloadLo(uint8_t val, uint8_t x);
        void setTimerXReloadHi(uint8_t val, uint8_t x);

        void setTimerXControlLo(uint8_t val, uint8_t x);
        void setTimerXControlHi(uint8_t val, uint8_t x);

        void queueTimerInterrupt(uint8_t x);

        void calculateTimerXCounter(uint8_t x, uint64_t cyclesPassed);

        uint32_t timerPrescaler[4] = {1, 1, 1, 1};

        bool timerStart[4] = {false, false, false, false};

        uint32_t timerExcessCycles[4] = {0, 0, 0, 0};

        uint64_t timerCycleOfLastUpdate[4] = {0, 0, 0, 0};

        uint32_t timerCounter[4] = {0, 0, 0, 0};

        uint32_t timerReload[4] = {0, 0, 0, 0};

        bool timerCountUp[4] = {false, false, false, false};

        bool timerIrqEnable[4] = {false, false, false, false};


        std::shared_ptr<Bus> bus;
        std::shared_ptr<ARM7TDMI> cpu;
        std::shared_ptr<Scheduler> scheduler;

};


<|start_filename|>src/GameBoyAdvance.cpp<|end_filename|>

#include "../include/GameBoyAdvance.hpp"
#include "GameBoyAdvanceImpl.h"
#include <iostream>


GameBoyAdvance::GameBoyAdvance() {
    pimpl = std::make_shared<GameBoyAdvanceImpl>();
}


bool GameBoyAdvance::loadRom(std::string path) {
    return pimpl->loadRom(path);
}

void GameBoyAdvance::enableDebugger() {
    // TODO
} 

void GameBoyAdvance::setBreakpoint(uint32_t address) {
    // TODO
}

void GameBoyAdvance::printCpuState() {
    pimpl->printCpuState();
} 

void GameBoyAdvance::runRom() {
    pimpl->enterMainLoop();
}





<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbAddSubHandler.h<|end_filename|>
#pragma once

#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

// op = instruction & 0xFFC0;
// anything within the mask can be constexpr!

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbAddSubHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xF800) == 0x1800);
    uint8_t rd = thumbGetRd(instruction);
    uint8_t rs = thumbGetRs(instruction);
    uint32_t op2;
    uint32_t result;
    uint32_t rsVal = cpu->getRegister(rs);

    bool carryFlag;
    bool overflowFlag;
    bool zeroFlag;
    bool signFlag;

    //uint8_t opcode = (instruction & 0x0600) >> 9;
    //01 1000
    constexpr uint8_t opcode = (op & 0x018) >> 3;

    if constexpr(opcode == 0) {
        // 0: ADD{S} Rd,Rs,Rn   ;add register Rd=Rs+Rn
        op2 = cpu->getRegister((instruction & 0x01C0) >> 6);
        result = rsVal + op2;
        carryFlag = aluAddSetsCarryBit(rsVal, op2);
        overflowFlag = aluAddSetsOverflowBit(rsVal, op2, result);
    } else if constexpr(opcode == 1) {
        // 1: SUB{S} Rd,Rs,Rn   ;subtract register   Rd=Rs-Rn
        op2 = cpu->getRegister((instruction & 0x01C0) >> 6);
        result = rsVal - op2;
        carryFlag = aluSubtractSetsCarryBit(rsVal, op2);
        overflowFlag = aluSubtractSetsOverflowBit(rsVal, op2, result);
    } else if constexpr(opcode == 2) {
        // 2: ADD{S} Rd,Rs,#nn  ;add immediate       Rd=Rs+nn
        op2 = (instruction & 0x01C0) >> 6;
        result = rsVal + op2;
        carryFlag = aluAddSetsCarryBit(rsVal, op2);
        overflowFlag = aluAddSetsOverflowBit(rsVal, op2, result);
    } else {
        // opcode == 3
        // 3: SUB{S} Rd,Rs,#nn  ;subtract immediate  Rd=Rs-nn
        op2 = (instruction & 0x01C0) >> 6;
        result = rsVal - op2;
        carryFlag = aluSubtractSetsCarryBit(rsVal, op2);
        overflowFlag = aluSubtractSetsOverflowBit(rsVal, op2, result);
    }
    signFlag = aluSetsSignBit(result);
    zeroFlag = aluSetsZeroBit(result);

    cpu->setRegister(rd, result);
    cpu->cpsr.C = carryFlag;
    cpu->cpsr.Z = zeroFlag;
    cpu->cpsr.N = signFlag;
    cpu->cpsr.V = overflowFlag;
  
    return SEQUENTIAL;
}


<|start_filename|>src/util/static_for.h<|end_filename|>
template <int First, int Last>
struct static_for
{
    template <typename Lambda>
    static inline constexpr void apply(Lambda const& f) {
        if (First < Last)
        {
            f(std::integral_constant<int, First>{});
            static_for<First + 1, Last>::apply(f);
        }
    }
};
template <int N>
struct static_for<N, N>
{
    template <typename Lambda>
    static inline constexpr void apply(Lambda const& f) {}
};


<|start_filename|>src/Scheduler.cpp<|end_filename|>
#include "Scheduler.h"
#include "GameBoyAdvanceImpl.h"
#include <iostream>
#include "util/macros.h"

#include "assert.h"


void Scheduler::addEvent(EventType eventType, uint64_t cyclesInFuture, EventCondition eventCondition, bool ignoreCondition) {
    // printEventList();
    uint64_t startAt = GameBoyAdvanceImpl::cyclesSinceStart + cyclesInFuture;

    EventNode* node = &events[eventType];
    node->event.active = true;
    node->event.startCycle = startAt;
    node->next = nullptr;
    node->event.eventCondition = eventCondition;

    if(startNode == nullptr && eventCondition == NULL_CONDITION) {
        startNode = node;
    } else {
        removeNode(node);
        EventNode* curr = startNode;
        EventNode* prev = nullptr;
        while(curr != nullptr) {
            if(eventCondition == NULL_CONDITION || ignoreCondition) {
                if((curr->event.startCycle > startAt) || 
                (curr->event.startCycle == startAt && eventType < curr->event.eventType)) {
                    break;
                } 
            } else {
                bool found = false;
                switch(eventCondition) {
                    case EventCondition::HBLANK_START:
                    case EventCondition::DMA3_VIDEO_MODE: {
                        if(curr->event.eventType == EventType::HBLANK ) {
                            found = true;
                            node->event.startCycle = curr->event.startCycle;
                        }
                        break;
                    }
                    case EventCondition::VBLANK_START: {
                        if(curr->event.eventType == EventType::VBLANK) {
                            found = true;
                            node->event.startCycle = curr->event.startCycle;
                        }                                
                        break;
                    }
                    default: {
                        assert(false);
                        break;
                    }
                }
                if(found) {
                    // putting the event condition event *after* the event that activates the condition
                    prev = curr;
                    curr = curr->next;
                    while(curr != nullptr && 
                          curr->event.eventType < eventType && 
                          curr->event.eventCondition == eventCondition) {
                        // make sure DMA priority is maintained
                        curr = curr->next;
                    }
                    break;
                }
            }
            prev = curr;
            curr = curr->next;
        }

        node->next = curr;
        if(prev != nullptr) {
            prev->next = node;
        } else {
            startNode = node;
        }
    }
}

void Scheduler::removeNode(EventNode* eventNode) {
    if(startNode == nullptr) {
        startNode = nullptr;
    } else if(startNode == eventNode){
        startNode = startNode->next;
    } else {
        EventNode* curr = startNode;
        while(curr->next != nullptr) {
            if(curr->next == eventNode) {
                curr->next = curr->next->next;
                break;
            } 
            curr = curr->next;
        }
    }
    eventNode->next = nullptr;
}

Scheduler::Event* Scheduler::getNextEvent(uint64_t currentCycle) {
    Event* toReturn = nullptr;

    if(startNode != nullptr && startNode->event.startCycle <= currentCycle) {
        toReturn = &startNode->event;
        EventNode* oldStart = startNode;
        startNode = startNode->next;
        oldStart->next = nullptr;
    }    

    return toReturn;
}

Scheduler::Event* Scheduler::peekNextEvent() {
    Event* toReturn = nullptr;
    if(startNode != nullptr) {
        toReturn = &startNode->event;
    }
    return toReturn;
}

void Scheduler::removeEvent(EventType eventType) {
    EventNode* node = &events[eventType];
    removeNode(node);

}

void Scheduler::printEventList() {
    std::cout << "[\n";
    EventNode* curr = startNode;
    int size = 0;
    while(curr != nullptr) {
        size++;
        std::cout << "{eventType: " << (curr->event.eventType) 
                  << " startCycle: " << curr->event.startCycle 
                  << " active: " << curr->event.active 
                  <<  " eventCond: " << curr->event.eventCondition 
                  << "},\n";
        curr = curr->next;
    }
    std::cout << "]\n";

}  

<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbLongBHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbLongBHandler(uint16_t instruction, ARM7TDMI* cpu) {
    //uint8_t opcode = (instruction & 0xF800) >> 11;
    constexpr uint8_t opcode = (op & 0x3E0) >> 5;

    if constexpr(opcode == 0x1E) {
        // First Instruction - LR = PC+4+(nn SHL 12)
        assert((instruction & 0xF800) == 0xF000);
        uint32_t offsetHiBits = signExtend23Bit(((uint32_t)(instruction & 0x07FF)) << 12);            
        cpu->setRegister(LINK_REGISTER, cpu->getRegister(PC_REGISTER) + 2 + offsetHiBits);
        return SEQUENTIAL;
    } else if constexpr(opcode == 0x1F) {
        // Second Instruction - PC = LR + (nn SHL 1), and LR = PC+2 OR 1 (and BLX: T=0)
        // 11111b: BL label   ;branch long with link
        uint32_t offsetLoBits = ((uint32_t)(instruction & 0x07FF)) << 1;
        uint32_t temp = (cpu->getRegister(PC_REGISTER)) | 0x1;
        // The destination address range is (PC+4)-400000h..+3FFFFEh, 
        cpu->setRegister(PC_REGISTER, (cpu->getRegister(LINK_REGISTER) + offsetLoBits));
        cpu->setRegister(LINK_REGISTER, temp);  
        return BRANCH;
    } else if constexpr(opcode == 0x1D) {
        // 11101b: BLX label  ;branch long with link switch to ARM mode (ARM9)
        DEBUGWARN("BLX not implemented!\n");
        assert(false);
    } else {
        assert(false);
    }

    return SEQUENTIAL;
}


<|start_filename|>src/arm7tdmi/ARMInstructions/ArmUndefHandler.h<|end_filename|>

#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"
#include <bitset>

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::armUndefHandler(uint32_t instruction, ARM7TDMI* cpu) {
    DEBUGWARN("UNDEFINED ARM OPCODE! " << std::bitset<32>(instruction).to_string() << std::endl);
    cpu->switchToMode(ARM7TDMI::Mode::UNDEFINED);
    *(cpu->currentSpsr) = cpu->cpsr;
    cpu->cpsr.Mode = Mode::UNDEFINED;
    cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
    return BRANCH;
}

<|start_filename|>src/arm7tdmi/ARM7TDMI.h<|end_filename|>
#pragma once

#include <array>
#include <cstdint>
#include <iostream>
#include <string>
#include <vector>
#include <deque>
#include <memory>
#include "../util/static_for.h"
#include "../util/macros.h"

#include "assert.h"


class Bus;

class ARM7TDMI {

   public:
    ~ARM7TDMI();

    void initializeWithRom();
    uint32_t step();

    void clock();

    // CPU exceptions
    void irq();
    void firq();
    void reset();

    // dependency injection
    void connectBus(std::shared_ptr<Bus> bus);

    // struct representing program status register (xPSR)
    struct ProgramStatusRegister {
        uint8_t Mode : 5;  //  M4-M0 - Mode Bits
        uint8_t T : 1;  // T - State Bit       (0=ARM, 1=THUMB) - Do not change
                        // manually!
        uint8_t F : 1;  // F - FIQ disable     (0=Enable, 1=Disable)
        uint8_t I : 1;  // I - IRQ disable     (0=Enable, 1=Disable)
        uint32_t Reserved : 19;  // Reserved            (For future use) - Do
                                 // not change manually!
        uint8_t Q : 1;  // Q - Sticky Overflow (1=Sticky Overflow, ARMv5TE and
                        // up only)
        uint8_t V : 1;  // V - Overflow Flag   (0=No Overflow, 1=Overflow)
        uint8_t C : 1;  // C - Carry Flag      (0=Borrow/No Carry, 1=Carry/No Borrow
        uint8_t Z : 1;  // Z - Zero Flag       (0=Not Zero, 1=Zero)
        uint8_t N : 1;  // N - Sign Flag       (0=Not Signed, 1=Signed)
    };

    // struct representing the number of cycles an operation will take
    struct Cycles {
        uint8_t nonSequentialCycles : 8;
        uint8_t sequentialCycles : 8;
        uint8_t internalCycles : 8;
        uint8_t waitState : 8;
    };

    enum Interrupt : uint16_t {
        VBlank         = 0b1,
        HBlank         = 0b10,
        VCounterMatch  = 0b100,
        Timer0Overflow = 0b1000,
        Timer1Overflow = 0b10000,
        Timer2Overflow = 0b100000,
        Timer3Overflow = 0b1000000,
        SerialComm     = 0b10000000,
        DMA0           = 0b100000000,
        DMA1           = 0b1000000000,
        DMA2           = 0b10000000000,
        DMA3           = 0b100000000000,
        Keypad         = 0b1000000000000,
        GamePak        = 0b10000000000000
    };

    void queueInterrupt(Interrupt interrupt);

    uint64_t thumbCount = 0;
    uint64_t armCount = 0;

    // returns the SPSR for the CPU's current mode
    ProgramStatusRegister *getCurrentModeSpsr();

    ProgramStatusRegister getCpsr();

    // accounts for modes, ex in IRQ mode, getting register 14 will return value
    // of R14_irq


    void setCurrInstruction(uint32_t instruction);

    uint32_t getCurrentInstruction();
    Cycles getCurrentCycles();
    static uint32_t psrToInt(ProgramStatusRegister psr);

    ProgramStatusRegister cpsr = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    ProgramStatusRegister SPSR_fiq = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    ProgramStatusRegister SPSR_svc = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    ProgramStatusRegister SPSR_abt = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    ProgramStatusRegister SPSR_irq = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    ProgramStatusRegister SPSR_und = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

    
    static uint32_t aluShiftRor(uint32_t value, uint8_t shift);
    static uint32_t aluShiftRrx(uint32_t value, uint8_t shift, ARM7TDMI *cpu);

    uint32_t getRegister(uint8_t index);
    void setRegister(uint8_t index, uint32_t value);
  
   private:
    
    uint32_t getUserRegister(uint8_t index);
    
    // accounts for modes, ex in IRQ mode, setting register 14 will set value of
    // R14_irq
    void setUserRegister(uint8_t index, uint32_t value);

    bool interruptsEnabled();

    enum FetchPCMemoryAccess {
        NONSEQUENTIAL,
        SEQUENTIAL,
        BRANCH,
        NONE
    };

    FetchPCMemoryAccess currentPcAccessType;

    uint32_t currInstruction;
    uint32_t currInstrAddress; 

    void getNextInstruction(FetchPCMemoryAccess currentPcAccessType);


    uint32_t thumbLongbranchShift = 0;

    union BitPreservedInt32 {
        int32_t _signed;
        uint32_t _unsigned;
    };

    union BitPreservedInt64 {
        int64_t _signed;
        uint64_t _unsigned;
    };

    //registers can be dynamically changed to support different registers for
    // different CPU modes
    std::array<uint32_t *, 16> registers = {
        &r0,  &r1, &r2, &r3, &r4, &r5, &r6, &r7, &r8, &r9, &r10, &r11, &r12,
        &r13,  // stack pointer
        &r14,  // link register
        &r15   // program counter
    };

    std::array<uint32_t *, 16> userRegisters = {
        &r0,  &r1, &r2, &r3, &r4, &r5, &r6, &r7, &r8, &r9, &r10, &r11, &r12,
        &r13,  // stack pointer
        &r14,  // link register
        &r15   // program counter
    };

    // all the possible registers
    uint32_t r0 = 0;
    uint32_t r1 = 0;
    uint32_t r2 = 0;
    uint32_t r3 = 0;
    uint32_t r4 = 0;
    uint32_t r5 = 0;
    uint32_t r6 = 0;
    uint32_t r7 = 0;
    uint32_t r8 = 0;
    uint32_t r9 = 0;
    uint32_t r10 = 0;
    uint32_t r11 = 0;
    uint32_t r12 = 0;
    uint32_t r13 = 0;
    uint32_t r14 = 0;
    uint32_t r15 = 0;
    uint32_t r8_fiq = 0x0;
    uint32_t r9_fiq = 0x0;
    uint32_t r10_fiq = 0x0;
    uint32_t r11_fiq = 0x0;
    uint32_t r12_fiq = 0x0;
    uint32_t r13_fiq = 0x0;
    uint32_t r14_fiq = 0x0;
    uint32_t r13_irq = 0x0;
    uint32_t r14_irq = 0x0;
    uint32_t r13_svc = 0x0;
    uint32_t r14_svc = 0x0;
    uint32_t r13_abt = 0x0;
    uint32_t r14_abt = 0x0;
    uint32_t r13_und = 0x0;
    uint32_t r14_und = 0x0;


    static constexpr uint8_t PC_REGISTER = 15;
    static constexpr uint8_t LINK_REGISTER = 14;
    static constexpr uint8_t SP_REGISTER = 13;
    // TODO: temporary boot location for testing
    static constexpr uint32_t BOOT_LOCATION = 0x08000000;
    //static const uint32_t BOOT_LOCATION = 0x00000000;

    uint8_t overflowBit = 0;
    uint8_t carryBit = 0;
    uint8_t zeroBit = 0;
    uint8_t signBit = 0;

    // todo: deprecate in favour of shifting op2 in place and only returning carry
    struct AluShiftResult {
        uint32_t op2;
        uint8_t carry;
    };

    enum Mode {
        USER = 16,
        FIQ = 17,
        IRQ = 18,
        SUPERVISOR = 19,
        ABORT = 23,
        UNDEFINED = 27,
        SYSTEM = 31
    };

    ProgramStatusRegister *currentSpsr;

    std::shared_ptr<Bus> bus;

    Cycles UNDEF(uint32_t instruction);

    Cycles execAluOpcode(uint8_t opcode, uint32_t rd, uint32_t op1,
                         uint32_t op2);

    static uint32_t aluShiftLsl(uint32_t value, uint8_t shift);
    static uint32_t aluShiftLsr(uint32_t value, uint8_t shift);
    static uint32_t aluShiftAsr(uint32_t value, uint8_t shift);

    static bool aluSetsZeroBit(uint32_t value);
    static bool aluSetsSignBit(uint32_t value);
    static bool aluSubtractSetsOverflowBit(uint32_t rnValue, uint32_t op2,
                                           uint32_t result);
    static bool aluSubtractSetsCarryBit(uint32_t rnValue, uint32_t op2);
    static bool aluAddSetsCarryBit(uint32_t rnValue, uint32_t op2);
    static bool aluAddSetsOverflowBit(uint32_t rnValue, uint32_t op2,
                                      uint32_t result);
    static bool aluAddWithCarrySetsCarryBit(uint64_t result);
    static bool aluAddWithCarrySetsOverflowBit(uint32_t rnValue, uint32_t op2,
                                               uint32_t result, ARM7TDMI *cpu);
    static bool aluSubWithCarrySetsCarryBit(uint64_t result);
    static bool aluSubWithCarrySetsOverflowBit(uint32_t rnValue, uint32_t op2,
                                               uint32_t result, ARM7TDMI *cpu);

    static bool aluLslSetsCarryBit(uint32_t value, uint32_t offset);
    static bool aluLsrSetsCarryBit(uint32_t value, uint32_t offset);
    static bool aluRorSetsCarryBit(uint32_t value, uint32_t offset);
    static bool aluAsrSetsCarryBit(uint32_t value, uint32_t offset);



    static uint8_t getRd(uint32_t instruction);
    static uint8_t getRn(uint32_t instruction);
    static uint8_t getRs(uint32_t instruction);
    static uint8_t getRm(uint32_t instruction);
    static uint8_t getOpcode(uint32_t instruction);

    static uint8_t thumbGetRs(uint16_t instruction);
    static uint8_t thumbGetRd(uint16_t instruction);
    static uint8_t thumbGetRb(uint16_t instruction);
        
    static bool dataTransGetP(uint32_t instruction);
    static bool dataTransGetU(uint32_t instruction);
    static bool dataTransGetB(uint32_t instruction);
    static bool dataTransGetW(uint32_t instruction);
    static bool dataTransGetL(uint32_t instruction);

    static bool sFlagSet(uint32_t instruction);

    static uint8_t mulGetExecutionTimeMVal(uint32_t value);
    static uint8_t umullGetExecutionTimeMVal(uint32_t value);

    static uint32_t signExtend9Bit(uint16_t value);
    static uint32_t signExtend12Bit(uint32_t value);
    static uint32_t signExtend23Bit(uint32_t value);
    
    void transferToPsr(uint32_t value, uint8_t field,
                       ProgramStatusRegister *psr);
    

    enum AluOpcode {
        AND = 0x0,
        EOR = 0x1,
        SUB = 0x2,
        RSB = 0x3,
        ADD = 0x4,
        ADC = 0x5,
        SBC = 0x6,
        RSC = 0x7,
        TST = 0x8,
        TEQ = 0x9,
        CMP = 0xA,
        CMN = 0xB,
        ORR = 0xC,
        MOV = 0xD,
        BIC = 0xE,
        MVN = 0xF
    };



    enum Condition {
        EQ = 0x0, // 0:   EQ     Z=1           equal (zero) (same)
        NE = 0x1, // 1:   NE     Z=0           not equal (nonzero) (not same)
        CS = 0x2, // 2:   CS/HS  C=1           unsigned higher or same (carry set)
        CC = 0x3, // 3:   CC/LO  C=0           unsigned lower (carry cleared)
        MI = 0x4, // 4:   MI     N=1           signed negative (minus)
        PL = 0x5, // 5:   PL     N=0           signed positive or zero (plus)
        VS = 0x6, // 6:   VS     V=1           signed overflow (V set)
        VC = 0x7, // 7:   VC     V=0           signed no overflow (V cleared)
        HI = 0x8, // 8:   HI     C=1 and Z=0   unsigned higher
        LS = 0x9, // 9:   LS     C=0 or Z=1    unsigned lower or same
        GE = 0xA, // A:   GE     N=V           signed greater or equal
        LT = 0xB, // B:   LT     N<>V          signed less than
        GT = 0xC, // C:   GT     Z=0 and N=V   signed greater than
        LE = 0xD, // D:   LE     Z=1 or N<>V   signed less or equal
        AL = 0xE, // E:   AL     -             always (the "AL" suffix can be omitted)
        NV = 0xF  // F:   NV     -             never (ARMv1,v2 only) (Reserved ARMv3 and up)
    };

    bool conditionalHolds(uint8_t cond);

    void switchToMode(Mode mode);

    // TODO: temporary
    friend class Debugger;

    using ArmHandler = FetchPCMemoryAccess (*)(uint32_t instruction, ARM7TDMI* cpu);
    using ThumbHandler = FetchPCMemoryAccess (*)(uint16_t instruction, ARM7TDMI* cpu);

    template<uint16_t op> static FetchPCMemoryAccess armDataProcHandler(uint32_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess armPsrHandler(uint32_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess armMultHandler(uint32_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess armStdHandler(uint32_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess armUndefHandler(uint32_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess armHwdtHandler(uint32_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess armSdsHandler(uint32_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess armBdtHandler(uint32_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess armSdtHandler(uint32_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess armBranchHandler(uint32_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess armBranchXHandler(uint32_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess armSwiHandler(uint32_t instruction, ARM7TDMI* cpu);

    template<uint16_t op> static FetchPCMemoryAccess thumbAddOffSpHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbAddSubHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbAluHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbBHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbBxHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbCondBHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbImmHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbLongBHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbLdPcRelHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbLdStHwHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbLdStImmOffHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbLdStRegOffHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbLdStSeBHwHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbLdStSpRelHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbMultLdStHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbMultLdStPushPopHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbRelAddrHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbShiftHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbSwiHandler(uint16_t instruction, ARM7TDMI* cpu);
    template<uint16_t op> static FetchPCMemoryAccess thumbUndefHandler(uint16_t instruction, ARM7TDMI* cpu);


    static constexpr std::array<ArmHandler, 4096> armLut = [] {
        std::array<ArmHandler, 4096> lut = {};

        /*
        BX:
            0001 0010 00x1 (11)
        Swap:
            0001 0x00 1001 (11)
        PSR:
            0001 0xx0 0000 (10)
        Mult:
            0000 00xx 1001 (10)
        MulLong:
            0000 1xxx 1001 (9)
        PSR:
            0011 0x10 xxxx (7)
        SDT:
            (halfword)
            000x x0xx 1xx1 (6)
            000x x1xx 1xx1 (6)
        Dataproc:
            000x xxxx 0xx1 (5)
            000x xxxx xxx0 (4)
            001x xxxx xxxx (4)
        Undefined:
            011x xxxx xxx1 (4)
        SWI:
            1111 xxxx xxxx (4)
        SDT:
            (single)
            011x xxxx xxx0 (4)
            010x xxxx xxxx (3)
        BlockTrans:
            100x xxxx xxxx (3)
        Branch:
            101x xxxx xxxx (3)
        */
        auto decodeHandler = [&] (auto i) {
            switch((i & 0xE00) >> 9) {
                case 0b000: {
                    if((i & 0b000111111101) == 0b000100100001) {
                        return &armBranchXHandler<i.value>;
                    } else if((i & 0b000110111111) == 0b000100001001) {
                        return &armSdsHandler<i.value>;
                    } else if((i & 0b000110011111) == 0b000100000000) {
                        return &armPsrHandler<i.value>;
                    } else if((i & 0b000111001111) == 0b000000001001) {
                        return &armMultHandler<i.value>;
                    } else if((i & 0b000110001111) == 0b000010001001) {
                        return &armMultHandler<i.value>;
                    } else if((i & 0b000000001001) == 0b000000001001) {
                        return &armHwdtHandler<i.value>;
                    } else if((i & 0b000000001001) == 0b000000000001) {
                        return &armDataProcHandler<i.value>;
                    } else if((i & 0b000000000001) == 0b000000000000) {
                        return &armDataProcHandler<i.value>;
                    } else {
                    }
                    break;
                }
                case 0b001: {
                    if((i & 0b000110110000) == 0b000100100000) {
                        return &armPsrHandler<i.value>;
                    } else { 
                        return &armDataProcHandler<i.value>;
                    }                   
                    break;
                } 
                case 0b011: {
                    if(i & 0x1) {
                        return &armUndefHandler<i.value>;
                    } else {
                        return &armSdtHandler<i.value>;
                    }
                    break;
                }
                case 0b111: {
                    return &armSwiHandler<i.value>;
                }
                case 0b010: {
                    return &armSdtHandler<i.value>;
                }
                case 0b100: {
                    return &armBdtHandler<i.value>;
                }
                case 0b101: {
                    return &armBranchHandler<i.value>;
                }
                default: {
                    return &armUndefHandler<i.value>;
                }
            }
            return &armUndefHandler<i.value>;
        };
        /* 
            Since using recursive template metaprogramming, we have to
            build the LUT in chunks of 500 to avoid
            compiler stack overflow....
        */
        static_for<0, 500>::apply([&](auto i)
        {     
            lut[i] = decodeHandler(i);    
        });
        static_for<500, 1000>::apply([&](auto i)
        {   
            lut[i] = decodeHandler(i);           
        });
        static_for<1000, 1500>::apply([&](auto i)
        {            
            lut[i] = decodeHandler(i);
        });
        static_for<1500, 2000>::apply([&](auto i)
        {            
            lut[i] = decodeHandler(i);
        });
        static_for<2000, 2500>::apply([&](auto i)
        {            
            lut[i] = decodeHandler(i);
        });
        static_for<2500, 3000>::apply([&](auto i)
        {            
            lut[i] = decodeHandler(i);
        });
        static_for<3000, 3500>::apply([&](auto i)
        {            
            lut[i] = decodeHandler(i);
        });
        static_for<3500, 4000>::apply([&](auto i)
        {            
            lut[i] = decodeHandler(i);
        });
        static_for<4000, 4096>::apply([&](auto i)
        {            
            lut[i] = decodeHandler(i);
        });
        return lut;
    }();

    static constexpr std::array<ThumbHandler, 1024> thumbLut = [] {
        std::array<ThumbHandler, 1024> lut2 = {};
        /*
            add sp,nn
                1011 0000 xx (8)
            swi
                1101 1111 xx (8)
            alu
                0100 00xx xx (6)
            hireg/bx
                0100 01xx xx (6)
            push/pop
                1011 x10x xx (6)
            add/sub
                0001 1xxx xx (5)
            B
                1110 0xxx xx (5)
            ldr pc
                0100 1xxx xx (5)
            ldr str
                0101 xx0x xx (5)
            ldr h/sb/sh
                0101 xx1x xx (5)
            ldr H
                1000 xxxx xx (4)
            ldr sp
                1001 xxxx xx (4)
            add pc/sp
                1010 xxxx xx (4)
            stm/ldm
                1100 xxxx xx (4)
            b{cond}
                1101 xxxx xx (4)
            BL,BLX
                1111 xxxx xx (4)
            shift
                000x xxxx xx (3)
            imm
                001x xxxx xx (3)
            ldr {B}
                011x xxxx xx (3)
        */
        auto decodeThumbHandler = [&] (auto i) {
            switch ((i & 0b01110000000) >> 7) {  // mask 1
                case 0b0: { // case 000
                    if((i & 0b0001100000) == 0b0001100000) {
                        // 2: 00011xxxxxxxxxxx ADD/SUB
                        return &thumbAddSubHandler<i>;
                    } else {
                        // 1: 000xxxxxxxxxxxxx Shifted
                        return &thumbShiftHandler<i>;
                    }
                }
                case 0b001: { // case 001
                    // 3: 001xxxxxxxxxxxxx Immedi.
                    return &thumbImmHandler<i>;
                }
                case 0b010: { // case 010
                    switch(i & 0b0001000000) {
                        case 0b0000000000: {
                            switch(i & 0b0001110000) {
                                case 0b0000000000: {
                                    // 4: 010000xxxxxxxxxx AluOp     
                                    return &thumbAluHandler<i.value>;              
                                }
                                case 0b0000010000: {
                                    // 5: 010001xxxxxxxxxx HiReg/BX
                                    return &thumbBxHandler<i.value>;
                                }
                                default: {
                                    // 6: 01001xxxxxxxxxxx LDR PC
                                    return &thumbLdPcRelHandler<i.value>;
                                }
                            }
                        }
                        case 0b0001000000: {
                            if(i & 0b0000001000) {
                                // 8: 0101xx1xxxxxxxxx ""H/SB/SH
                                return &thumbLdStSeBHwHandler<i.value>;
                            } else {
                                // 7: 0101xx0xxxxxxxxx LDR/STR
                                return &thumbLdStRegOffHandler<i.value>;
                            }
                        }
                        default: {
                            break;
                        }                
                    }
                    break;
                }
                case 0b011: { // case 011
                    // 9: 011xxxxxxxxxxxxx ""{B}
                    return &thumbLdStImmOffHandler<i.value>;
                }
                case 0b100: { // case 100
                    if(i & 0b0001000000) {
                        // 11: 1001xxxxxxxxxxxx "" SP
                        return &thumbLdStSpRelHandler<i.value>;
                    } else {
                        // 10: 1000xxxxxxxxxxxx "H
                        return &thumbLdStHwHandler<i.value>;
                    }
                    break;
                }
                case 0b101: { // case 101
                    if(i & 0b0001000000) {
                        if(i & 0b0000010000) {
                            // 14: 1011x10xxxxxxxxx PUSH/POP
                            return &thumbMultLdStPushPopHandler<i.value>;
                        } else {
                            // 13: 10110000xxxxxxxx ADD SP,nn
                            return &thumbAddOffSpHandler<i.value>;
                        }
                    } else {
                        // 12: 1010xxxxxxxxxxxx ADD PC/SP
                        return &thumbRelAddrHandler<i.value>;
                    }
                }
                case 0b110: { // case 110
                    if(i & 0b0001000000) {
                        if((i & 0b0001111100) == 0b0001111100) {
                            // 17: 11011111xxxxxxxx SWI
                            return &thumbSwiHandler<i.value>;
                        } else {
                            // 16: 1101xxxxxxxxxxxx B{cond}
                            return &thumbCondBHandler<i.value>;
                        }
                    } else {
                        // 15: 1100xxxxxxxxxxxx STM/LDM
                        return &thumbMultLdStHandler<i.value>;
                    }           
                }
                case 0b111: { // case 111
                    if(i & 0b0001000000) {
                        // 19: 1111xxxxxxxxxxxx BL,BLX
                        return &thumbLongBHandler<i.value>;
                    } else {
                        // 18: 11100xxxxxxxxxxx B
                        return &thumbBHandler<i.value>;
                    }
                    break;
                }        
                default: {
                    break;
                }
            }
            // undefined opcode
            return &thumbUndefHandler<i.value>;
        };
        /* 
            Since using recursive template metaprogramming, we have to
            build the LUT in chunks of 500 to avoid
            compiler stack overflow....
        */
        static_for<0, 500>::apply([&](auto i)
        {     
            lut2[i] = decodeThumbHandler(i);    
        });
        static_for<500, 1000>::apply([&](auto i)
        {   
            lut2[i] = decodeThumbHandler(i);           
        });
        static_for<1000, 1024>::apply([&](auto i)
        {            
            lut2[i] = decodeThumbHandler(i);
        });
        return lut2;
    }();
};

constexpr std::array<ARM7TDMI::ArmHandler, 4096> ARM7TDMI::armLut;
constexpr std::array<ARM7TDMI::ThumbHandler, 1024> ARM7TDMI::thumbLut;

inline
uint32_t ARM7TDMI::aluShiftLsl(uint32_t value, uint8_t shift) {
    if(shift >= 32) {
        return 0;
    } else {
        return value << shift;
    }
}

inline
uint32_t ARM7TDMI::aluShiftLsr(uint32_t value, uint8_t shift) {
    if(shift >= 32) {
        return 0;
    } else {
        return value >> shift;
    }
}

inline
uint32_t ARM7TDMI::aluShiftAsr(uint32_t value, uint8_t shift) {
    if(shift >= 32) {
        return (value & 0x80000000) ? 0xFFFFFFFF : 0x0;
    } else {
       return (value & 0x80000000) ? (~(~value >> shift)) : (value >> shift); 
    }
}


inline
uint32_t ARM7TDMI::aluShiftRor(uint32_t value, uint8_t shift) {
    //assert(shift < 32U);
    return (value >> shift) | (value << (-((int8_t)shift) & 31U));
}


inline
bool ARM7TDMI::aluSetsZeroBit(uint32_t value) { 
    return value == 0;
}

inline
bool ARM7TDMI::aluSetsSignBit(uint32_t value) { 
    return value >> 31; 
}

inline
bool ARM7TDMI::aluSubtractSetsCarryBit(uint32_t rnValue, uint32_t op2) {
    return !(rnValue < op2);
}
inline
bool ARM7TDMI::aluSubtractSetsOverflowBit(uint32_t rnValue, uint32_t op2,
                                          uint32_t result) {
    return ((rnValue ^ op2) & 0x80000000) && ((rnValue ^ result) & 0x80000000);
}
inline
bool ARM7TDMI::aluAddSetsCarryBit(uint32_t rnValue, uint32_t op2) {
    return (0xFFFFFFFFU - op2) < rnValue;
}
inline
bool ARM7TDMI::aluAddSetsOverflowBit(uint32_t rnValue, uint32_t op2,
                                     uint32_t result) {
    return !((rnValue ^ op2) & 0x80000000) && ((rnValue ^ result) & 0x80000000);
}
inline
bool ARM7TDMI::aluAddWithCarrySetsCarryBit(uint64_t result) {
    return result >> 32;
}
inline
bool ARM7TDMI::aluAddWithCarrySetsOverflowBit(uint32_t rnValue, uint32_t op2,
                                              uint32_t result, ARM7TDMI *cpu) {
    return !((rnValue ^ op2) & 0x80000000) && ((rnValue ^ result) & 0x80000000);
}
inline
bool ARM7TDMI::aluSubWithCarrySetsCarryBit(uint64_t result) {
    return !(result >> 32);
}

inline
bool ARM7TDMI::aluSubWithCarrySetsOverflowBit(uint32_t rnValue, uint32_t op2,
                                              uint32_t result, ARM7TDMI *cpu) {
    return ((rnValue ^ op2) & 0x80000000) && ((rnValue ^ result) & 0x80000000);
}

inline 
bool ARM7TDMI::aluLslSetsCarryBit(uint32_t value, uint32_t offset) {
    if(offset <= 32) {
        return (value >> (32 - offset)) & 0x1;
    } else {
        return 0;
    }
}

inline 
bool ARM7TDMI::aluLsrSetsCarryBit(uint32_t value, uint32_t offset) {
    // TODO: could this be done better?
    // TODO, yes, you are redoing the shift calculation, dont need to 
    if(offset <= 32 && offset > 0) {
        return ((value >> (offset - 1)) & 1);
    } else {
        return 0;
    }
}

inline 
bool ARM7TDMI::aluRorSetsCarryBit(uint32_t value, uint32_t offset) {
    return ((value >> ((offset % 32) - 1)) & 1);
}

inline 
bool ARM7TDMI::aluAsrSetsCarryBit(uint32_t value, uint32_t offset) {
    // TODO: could this be done better?
    if(offset < 32 && offset > 0) {
        return ((value >> (offset - 1)) & 1);
    } else {
        return value & 0x80000000;
    }
}

inline
uint32_t ARM7TDMI::psrToInt(ProgramStatusRegister psr) {
    return 0 | (((uint32_t)psr.N) << 31) | (((uint32_t)psr.Z) << 30) |
           (((uint32_t)psr.C) << 29) | (((uint32_t)psr.V) << 28) |
           (((uint32_t)psr.Q) << 27) | (((uint32_t)psr.Reserved) << 26) |
           (((uint32_t)psr.I) << 7) | (((uint32_t)psr.F) << 6) |
           (((uint32_t)psr.T) << 5) | (((uint32_t)psr.Mode) << 0);
}

inline
bool ARM7TDMI::dataTransGetP(uint32_t instruction) {
    return instruction & 0x01000000;
}

inline
bool ARM7TDMI::dataTransGetU(uint32_t instruction) {
    return instruction & 0x00800000;
}

inline
bool ARM7TDMI::dataTransGetB(uint32_t instruction) {
    return instruction & 0x00400000;
}

inline
bool ARM7TDMI::dataTransGetW(uint32_t instruction) {
    return instruction & 0x00200000;
}

inline
bool ARM7TDMI::dataTransGetL(uint32_t instruction) {
    return instruction & 0x00100000;
}

inline
void ARM7TDMI::switchToMode(Mode mode) {
    switch (mode) {
        case SYSTEM:
        case USER: {
            currentSpsr = &cpsr;
            registers[8] = &r8;
            registers[9] = &r9;
            registers[10] = &r10;
            registers[11] = &r11;
            registers[12] = &r12;
            registers[13] = &r13;
            registers[14] = &r14;
            break;
        }
        case FIQ: {
            currentSpsr = &SPSR_fiq;
            //*(getCurrentModeSpsr()) = cpsr;
            registers[8] = &r8_fiq;
            registers[9] = &r9_fiq;
            registers[10] = &r10_fiq;
            registers[11] = &r11_fiq;
            registers[12] = &r12_fiq;
            registers[13] = &r13_fiq;
            registers[14] = &r14_fiq;
            break;
        }
        case IRQ: {
            currentSpsr = &SPSR_irq;
            //*(getCurrentModeSpsr()) = cpsr;
            registers[13] = &r13_irq;
            registers[14] = &r14_irq;
            break;
        }
        case SUPERVISOR: {
            currentSpsr = &SPSR_svc;
            //*(getCurrentModeSpsr()) = cpsr;
            registers[13] = &r13_svc;
            registers[14] = &r14_svc;
            break;
        }
        case ABORT: {
            currentSpsr = &SPSR_abt;
            //*(getCurrentModeSpsr()) = cpsr;
            registers[13] = &r13_abt;
            registers[14] = &r14_abt;
            break;
        }
        case UNDEFINED: {
            currentSpsr = &SPSR_und;
            //*(getCurrentModeSpsr()) = cpsr;
            registers[13] = &r13_abt;
            registers[14] = &r14_abt;
            break;
        }
    }
    // SPSR_svc=CPSR   ;save CPSR flags
    
}

inline
void ARM7TDMI::transferToPsr(uint32_t value, uint8_t field,
                             ProgramStatusRegister *psr) {
    if (field & 0b1000) {
        // TODO: is this correct? it says   f =  write to flags field     Bit
        // 31-24 (aka _flg)
        psr->N = (bool)(value & 0x80000000);
        psr->Z = (bool)(value & 0x40000000);
        psr->C = (bool)(value & 0x20000000);
        psr->V = (bool)(value & 0x10000000);
        psr->Q = (bool)(value & 0x08000000);
        psr->Reserved = (psr->Reserved & 0b0001111111111111111) | ((value & 0x07000000) >> 8);
    }
    if (field & 0b0100) {
        // reserved, don't change
    }
    if (field & 0b0010) {
        // reserverd don't change
    }
    if (cpsr.Mode != USER) {
        if (field & 0b0001) {
            psr->I = (bool)(value & 0x00000080);
            psr->F = (bool)(value & 0x00000040);
            // t bit may not be changed, for THUMB/ARM switching use BX
            // instruction.
            //assert(!(bool)(value & 0x00000020));
            psr->T = (bool)(value & 0x00000020);
            uint8_t mode = (value & 0x00000010) | (value & 0x00000008) |
                           (value & 0x00000004) | (value & 0x00000002) |
                           (value & 0x00000001);
            psr->Mode = mode;
            
            // TODO: implemnt less hacky way to transfer psr
            if(psr == &cpsr) {
                // TODO" are we actually supposed to change cpu mode here? Or are just the bits being changed
                switchToMode(ARM7TDMI::Mode(mode));
                *currentSpsr = cpsr;
                cpsr.Mode = ARM7TDMI::Mode(mode);
            }   
        }
    }
}



/*
Execution Time: 1S+mI for MUL, and 1S+(m+1)I for MLA. 
Whereas 'm' depends on whether/how many most significant 
bits of Rs are all zero or all one. That is m=1 for 
Bit 31-8, m=2 for Bit 31-16, m=3 for Bit 31-24, and m=4 otherwise.
*/
inline
uint8_t ARM7TDMI::mulGetExecutionTimeMVal(uint32_t value) {
    uint32_t masked = value & 0xFFFFFF00;
    if(masked == 0xFFFFFF00 || !masked) {
        return 1;
    }
    masked >>= 8;
    if(masked == 0xFFFFFF00 || !masked) {
        return 2;
    }
    masked >>= 8;
    if(masked == 0xFFFFFF00 || !masked) {
        return 3;
    }
    return 4;
}

inline
uint8_t ARM7TDMI::umullGetExecutionTimeMVal(uint32_t value) {
    uint32_t masked = value & 0xFFFFFF00;
    if(!masked) {
        return 1;
    }
    masked >>= 8;
    if(!masked) {
        return 2;
    }
    masked >>= 8;
    if(!masked) {
        return 3;
    }
    return 4;
}


// not guaranteed to always be rn, check the spec first
inline
uint8_t ARM7TDMI::getRn(uint32_t instruction) {
    return (instruction & 0x000F0000) >> 16;
}

// not guaranteed to always be rd, check the spec first
inline
uint8_t ARM7TDMI::getRd(uint32_t instruction) {
    return (instruction & 0x0000F000) >> 12;
}

// not guaranteed to always be rs, check the spec first
inline
uint8_t ARM7TDMI::getRs(uint32_t instruction) {
    return (instruction & 0x00000F00) >> 8;
}

inline
uint8_t ARM7TDMI::getRm(uint32_t instruction) {
    return (instruction & 0x0000000F);
}

inline
uint32_t ARM7TDMI::aluShiftRrx(uint32_t value, uint8_t shift, ARM7TDMI *cpu) {
    assert(shift < 32U);
    uint32_t rrxMask = (cpu->cpsr).C;
    return ((value >> shift) | (value << (-shift & 31U))) | (rrxMask << 31U);
}

inline
uint32_t ARM7TDMI::signExtend9Bit(uint16_t value) {
    return (value & 0x0100) ? (((uint32_t)value) | 0xFFFFFE00) : value;
}

inline
uint32_t ARM7TDMI::signExtend12Bit(uint32_t value) {
    return (value & 0x00000800) ? (value | 0xFFFFF000) : value;
}

inline
uint32_t ARM7TDMI::signExtend23Bit(uint32_t value) {
    return (value & 0x00400000) ? (value | 0xFF800000) : value;
}


inline
uint8_t ARM7TDMI::thumbGetRs(uint16_t instruction) {
    return (instruction & 0x0038) >> 3;
}

inline
uint8_t ARM7TDMI::thumbGetRd(uint16_t instruction) {
    return (instruction & 0x0007);
}

inline
uint8_t ARM7TDMI::thumbGetRb(uint16_t instruction) {
    return (instruction & 0x0038) >> 3;
}


#include "ARMInstructions/ArmBdtHandler.h"       
#include "ARMInstructions/ArmBranchXHandler.h"   
#include "ARMInstructions/ArmHwdtHandler.h"      
#include "ARMInstructions/ArmMultHandler.h"      
#include "ARMInstructions/ArmSdsHandler.h"      
#include "ARMInstructions/ArmSwiHandler.h"
#include "ARMInstructions/ArmBranchHandler.h"    
#include "ARMInstructions/ArmDataProcHandler.h"  
#include "ARMInstructions/ArmPsrHandler.h"       
#include "ARMInstructions/ArmSdtHandler.h"       
#include "ARMInstructions/ArmUndefHandler.h"


#include "ThumbInstructions/ThumbAddOffSpHandler.h"          
#include "ThumbInstructions/ThumbCondBHandler.h"             
#include "ThumbInstructions/ThumbLdStRegOffHandler.h"        
#include "ThumbInstructions/ThumbMultLdStPushPopHandler.h"
#include "ThumbInstructions/ThumbAddSubHandler.h"            
#include "ThumbInstructions/ThumbImmHandler.h"               
#include "ThumbInstructions/ThumbLdStSeBHwHandler.h"         
#include "ThumbInstructions/ThumbRelAddrHandler.h"
#include "ThumbInstructions/ThumbAluHandler.h"               
#include "ThumbInstructions/ThumbLdPcRelHandler.h"           
#include "ThumbInstructions/ThumbLdStSpRelHandler.h"         
#include "ThumbInstructions/ThumbShiftHandler.h"
#include "ThumbInstructions/ThumbBHandler.h"                 
#include "ThumbInstructions/ThumbLdStHwHandler.h"            
#include "ThumbInstructions/ThumbLongBHandler.h"             
#include "ThumbInstructions/ThumbSwiHandler.h"
#include "ThumbInstructions/ThumbBxHandler.h"                
#include "ThumbInstructions/ThumbLdStImmOffHandler.h"        
#include "ThumbInstructions/ThumbMultLdStHandler.h"          
#include "ThumbInstructions/ThumbUndefHandler.h"

<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbMultLdStPushPopHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbMultLdStPushPopHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xF000) == 0xB000);
    assert((instruction & 0x0600) == 0x0400);
    //uint8_t opcode = (instruction & 0x0800) >> 11;
    constexpr bool opcode = (op & 0x020);
    //bool pcLrBit = instruction & 0x0100; // 1: PUSH LR (R14), or POP PC (R15)
    constexpr bool pcLrBit = (op & 0x004);
    uint8_t rList = instruction & 0x00FF;
    uint32_t spValue = cpu->getRegister(SP_REGISTER);
    bool firstAccess = true;
    bool branch = false;

    // In THUMB mode stack is always meant to be 'full descending', 
    // ie. PUSH is equivalent to 'STMFD/STMDB' and POP to 'LDMFD/LDMIA' in ARM mode.
    if constexpr(!opcode) {
        // 0: PUSH {Rlist}{LR}   ;store in memory, decrements SP (R13)
        if constexpr(pcLrBit) {
            spValue -=4;
            // TODO: just realized! for push, or backwards block memory, 
            // the first data access in the for loop might be the last memory access in reality
            cpu->bus->write32(spValue, cpu->getRegister(LINK_REGISTER), Bus::CycleType::NONSEQUENTIAL);
            firstAccess = false;
        }
        for(int i = 7; i >= 0; i--) {
            if(rList & 0x80) {
                spValue -= 4;
                if(firstAccess) {
                    cpu->bus->write32(spValue, cpu->getRegister(i), Bus::CycleType::NONSEQUENTIAL);
                    firstAccess = false;
                } else {
                    cpu->bus->write32(spValue, cpu->getRegister(i), Bus::CycleType::SEQUENTIAL);
                }
            }
            rList <<= 1;
        }
        cpu->setRegister(SP_REGISTER, spValue);
    } else {
        // 1: POP  {Rlist}{PC}   ;load from memory, increments SP (R13)
        for(int i = 0; i < 8; i++) {
            if(rList & 0x01) {
                if(firstAccess) {
                    cpu->setRegister(i, aluShiftRor(cpu->bus->read32(spValue, Bus::CycleType::NONSEQUENTIAL), 
                                                    (spValue & 3) * 8));
                    firstAccess = false;
                } else {
                    cpu->setRegister(i, aluShiftRor(cpu->bus->read32(spValue, Bus::CycleType::SEQUENTIAL), 
                                                    (spValue & 3) * 8));
                }
                spValue += 4;
            }
            rList >>= 1;
        }  

        if constexpr(pcLrBit) {
            // TODO, whether it's sequentiual or not might depend on whether rlist is empty
            cpu->setRegister(PC_REGISTER, aluShiftRor(cpu->bus->read32(spValue, Bus::CycleType::NONSEQUENTIAL), 
                                                    (spValue & 3) * 8) & 0xFFFFFFFE);
            spValue += 4;
        } 
        cpu->setRegister(SP_REGISTER, spValue);
    }

    cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);

    if(!branch) {
        return BRANCH;
    } else {
        return NONSEQUENTIAL;
    }
}

<|start_filename|>src/Scheduler.h<|end_filename|>

#pragma once

#include <cstdint>
#include <list>
#include <array>

class Scheduler {

    public: 
        //Scheduler();

        enum EventType {
            HBLANK = 0,
            VBLANK = 1,
            
            TIMER0 = 2,
            TIMER1 = 3,
            TIMER2 = 4,
            TIMER3 = 5,

            VBLANK_END = 6,
            HBLANK_END = 7,

            NULL_EVENT = 8,

            DMA0 = 9,
            DMA1 = 10,
            DMA2 = 11,
            DMA3 = 12,
        };

        enum EventCondition {
            DMA3_VIDEO_MODE = 0,
            VBLANK_START = 1,
            HBLANK_START = 2,
            NULL_CONDITION = 3
        };

        static constexpr inline uint8_t convertDmaTypeToDmaVal(EventType dma) {
            return dma - 9;
        };

        static constexpr inline uint8_t convertDmaValToDmaEvent(uint8_t x) {
            return x + 9;
        };


        struct Event {
            EventType eventType;
            uint64_t startCycle;
            bool active = true;
            EventCondition eventCondition;
        };

        void addEvent(EventType eventType, uint64_t cyclesInFuture, EventCondition EventCondition, bool ignoreCondition);
        void removeEvent(EventType eventType);

        /*
            get next event with a cycle start less than currentCycle. Removes the event from the queue

            If eventCondition != NONE, returned event is guaranteed to be one of [DMA0 - DMA3]
        */
        Event* getNextEvent(uint64_t currentCycle);

        Event* peekNextEvent();

        void printEventList();

    private: 
        struct EventNode {
            Event event;
            EventNode* next = nullptr;
            EventNode* prev = nullptr;
        };

         std::array<EventNode, 13> events = {{
                                    {{HBLANK, 0, false, NULL_CONDITION}, nullptr, nullptr}, 
                                    {{VBLANK, 0, false, NULL_CONDITION}, nullptr, nullptr},
                                    {{TIMER0, 0, false, NULL_CONDITION}, nullptr, nullptr},
                                    {{TIMER1, 0, false, NULL_CONDITION}, nullptr, nullptr},
                                    {{TIMER2, 0, false, NULL_CONDITION}, nullptr, nullptr},
                                    {{TIMER3, 0, false, NULL_CONDITION}, nullptr, nullptr},
                                    {{VBLANK_END, 0, false, NULL_CONDITION}, nullptr, nullptr},
                                    {{HBLANK_END, 0, false, NULL_CONDITION}, nullptr, nullptr},
                                    {{NULL_EVENT, 0, false, NULL_CONDITION}, nullptr, nullptr},
                                    {{DMA0, 0, false, NULL_CONDITION}, nullptr, nullptr},
                                    {{DMA1, 0, false, NULL_CONDITION}, nullptr, nullptr},
                                    {{DMA2, 0, false, NULL_CONDITION}, nullptr, nullptr},
                                    {{DMA3, 0, false, NULL_CONDITION}, nullptr, nullptr}
                                }};

        EventNode* startNode = nullptr;
        
        void removeNode(EventNode* eventNode);
        
};

<|start_filename|>src/PPU.h<|end_filename|>
#include <cstdint>
#include <vector>
#include <array>
#include <queue>
#include <memory>

class Bus; 
class Scheduler;

class PPU {

    public:
        PPU();
        ~PPU();

        void renderScanline(uint16_t scanline);
        void renderObject();
        bool isObjectDirty();

        static const uint32_t H_VISIBLE_CYCLES = 960;
        static const uint32_t H_BLANK_CYCLES = 272;
        static const uint32_t H_TOTAL = 1232;

        static const uint32_t V_VISIBLE_CYCLES = 197120;
        static const uint32_t V_BLANK_CYCLES = 83776;
        static const uint32_t V_TOTAL = 280896;
        
        static const uint32_t SCREEN_WIDTH = 240;
        static const uint32_t SCREEN_HEIGHT = 160;

        std::array<uint16_t, SCREEN_WIDTH * SCREEN_HEIGHT>& renderCurrentScreen();

        std::array<uint16_t, SCREEN_WIDTH * SCREEN_HEIGHT> pixelBuffer = {};

        void connectBus(std::shared_ptr<Bus> bus);

        void updateOamState(uint32_t address, uint8_t value);

        void setObjectsDirty();

    private:
        std::shared_ptr<Bus> bus; 
        std::shared_ptr<Scheduler> scheduler;

        uint32_t indexBgPalette4Bpp(uint8_t index);
        uint32_t indexBgPalette8Bpp(uint8_t index);
        uint32_t indexObjPalette4Bpp(uint8_t index);        
        uint32_t indexObjPalette8Bpp(uint8_t index);  
        uint16_t getBackdropColour();   

        const uint32_t transparentColour = 0x00040000;
        const uint32_t lowestPrio = 0x00030000;
        bool isTransparent(uint32_t pixelData);

        // each element of array: bits 0-15: colour, bit 16-17: drawMode, bit 18: transparent,
        // to find sprite pixel of priority i at location (x,y) -> [i * SCREEN_WIDTH * SCREEN_HEIGHT + y * SCREEN_WIDTH + x]
        std::array<uint32_t, SCREEN_WIDTH * SCREEN_HEIGHT * 4> spriteBuffer = {};

        // each element of array: bits 0-15: colour, bits 16-17: priority, bit 18: transparent
        // to find pixel of bg#i at location (x,y) -> [i * SCREEN_WIDTH * SCREEN_HEIGHT + y * SCREEN_WIDTH + x]
        std::array<uint32_t, SCREEN_WIDTH * SCREEN_HEIGHT * 4> bgBuffer = {};

        // backdrop colour for each scanline
        std::array<uint16_t, SCREEN_HEIGHT> scanlineBackDropColours;


        bool dirty;

        void renderSprites(uint16_t scanline);
        void renderBg(uint16_t scanline);
        void renderBgX(uint16_t scanline, uint8_t x);

        struct Dimension {
            uint8_t width;
            uint8_t height;
        };

        struct BgWindowData {
            bool enabled;
            uint8_t right;
            uint8_t left;
            uint8_t bottom;
            uint8_t top;
            /*
                Bit   Expl.
                0-3   Window x BG0-BG3 Enable Bits     (0=No Display, 1=Display)
                4     Window x OBJ Enable Bit          (0=No Display, 1=Display)
                5     Window x Color Special Effect    (0=Disable, 1=Enable)
            */
            uint8_t metaData;
        };

        struct Coords {
            int32_t x;
            int32_t y;
        };

        // to find window y data at scanline x: [x * SCREEN_HEIGHT + y]
        std::array<BgWindowData, SCREEN_HEIGHT * 2> scanlineBgWindowData;
        /*
            Bit   Expl.
            0-3   Outside BG0-BG3 Enable Bits      (0=No Display, 1=Display)
            4     Outside OBJ Enable Bit           (0=No Display, 1=Display)
            5     Outside Color Special Effect     (0=Disable, 1=Enable)
        */
        std::array<uint8_t, SCREEN_HEIGHT> scanlineOutsideWindowData;

        /*
            Bit   Expl.
            8-11  OBJ Window BG0-BG3 Enable Bits   (0=No Display, 1=Display)
            12    OBJ Window OBJ Enable Bit        (0=No Display, 1=Display)
            13    OBJ Window Color Special Effect  (0=Disable, 1=Enable)
        */
        std::array<uint8_t, SCREEN_HEIGHT> scanlineObjectWindowData;

        // in TILES, not pixels
        // [shape][size]
        Dimension spriteDimensions[3][4] = {
            { {1,1}, {2,2}, {4,4}, {8,8} },
            { {2,1}, {4,1}, {4,2}, {8,4} },
            { {1,2}, {1,4}, {2,4}, {4,8} }
        };

        Dimension textBgDimensions[4] = {
            {32, 32},
            {64, 32},
            {32, 64},
            {64, 64}
        };

        Coords convertScreenCoordsToSpriteCoords(int32_t x, int32_t y, int16_t pa, int16_t pb, int16_t pc, int16_t pd, int32_t xRotCentre, int32_t yRotCentre);


};

<|start_filename|>src/arm7tdmi/ARMInstructions/ArmLut.h<|end_filename|>


// Dataproc: 

/*

    BX:
        0001 0010 00x1 (11)
    Swap:
        0001 0x00 1001 (11)
    PSR:
        0001 0xx0 0000 (10)
    Mult:
        0000 00xx 1001 (10)
    MulLong:
        0000 1xxx 1001 (9)
    PSR:
        0011 0x10 xxxx (7)
    SDT:
        000x x0xx 1xx1 (6)
        000x x1xx 1xx1 (6)
    Dataproc:
        001x xxxx 0xx1 (5)
        000x xxxx xxx0 (4)
    Undefined:
        011x xxxx xxx1 (4)
    SWI:
        1111 xxxx xxxx (4)
    SDT:
        011x xxxx xxx0 (4)
        010x xxxx xxxx (3)
    BlockTrans:
        100x xxxx xxxx (3)
    Branch:
        101x xxxx xxxx (3)


  |_Cond__|0_0_0|___Op__|S|__Rn___|__Rd___|__Shift__|Typ|0|__Rm___| DataProc
  |_Cond__|0_0_0|___Op__|S|__Rn___|__Rd___|__Rs___|0|Typ|1|__Rm___| DataProc
  |_Cond__|0_0_1|___Op__|S|__Rn___|__Rd___|_Shift_|___Immediate___| DataProc
  |_Cond__|0_0_1_1_0_0_1_0_0_0_0_0_1_1_1_1_0_0_0_0|_____Hint______| ARM11:Hint
  |_Cond__|0_0_1_1_0|P|1|0|_Field_|__Rd___|_Shift_|___Immediate___| PSR Imm
  |_Cond__|0_0_0_1_0|P|L|0|_Field_|__Rd___|0_0_0_0|0_0_0_0|__Rm___| PSR Reg
  |_Cond__|0_0_0_1_0_0_1_0_1_1_1_1_1_1_1_1_1_1_1_1|0_0|L|1|__Rn___| BX,BLX
  |1_1_1_0|0_0_0_1_0_0_1_0|_____immediate_________|0_1_1_1|_immed_| ARM9:BKPT
  |_Cond__|0_0_0_1_0_1_1_0_1_1_1_1|__Rd___|1_1_1_1|0_0_0_1|__Rm___| ARM9:CLZ
  |_Cond__|0_0_0_1_0|Op_|0|__Rn___|__Rd___|0_0_0_0|0_1_0_1|__Rm___| ARM9:QALU
  |_Cond__|0_0_0_0_0_0|A|S|__Rd___|__Rn___|__Rs___|1_0_0_1|__Rm___| Multiply
  |_Cond__|0_0_0_0_0_1_0_0|_RdHi__|_RdLo__|__Rs___|1_0_0_1|__Rm___| ARM11:UMAAL
  |_Cond__|0_0_0_0_1|U|A|S|_RdHi__|_RdLo__|__Rs___|1_0_0_1|__Rm___| MulLong
  |_Cond__|0_0_0_1_0|Op_|0|Rd/RdHi|Rn/RdLo|__Rs___|1|y|x|0|__Rm___| MulHalfARM9
  |_Cond__|0_0_0_1_0|B|0_0|__Rn___|__Rd___|0_0_0_0|1_0_0_1|__Rm___| TransSwp12
  |_Cond__|0_0_0_1_1|_Op__|__Rn___|__Rd___|1_1_1_1|1_0_0_1|__Rm___| ARM11:LDREX
  |_Cond__|0_0_0|P|U|0|W|L|__Rn___|__Rd___|0_0_0_0|1|S|H|1|__Rm___| TransReg10
  |_Cond__|0_0_0|P|U|1|W|L|__Rn___|__Rd___|OffsetH|1|S|H|1|OffsetL| TransImm10
  |_Cond__|0_1_0|P|U|B|W|L|__Rn___|__Rd___|_________Offset________| TransImm9
  |_Cond__|0_1_1|P|U|B|W|L|__Rn___|__Rd___|__Shift__|Typ|0|__Rm___| TransReg9
  |_Cond__|0_1_1|________________xxx____________________|1|__xxx__| Undefined
  |_Cond__|0_1_1|Op_|x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x_x|1|x_x_x_x| ARM11:Media
  |1_1_1_1_0_1_0_1_0_1_1_1_1_1_1_1_1_1_1_1_0_0_0_0_0_0_0_1_1_1_1_1| ARM11:CLREX
  |_Cond__|1_0_0|P|U|S|W|L|__Rn___|__________Register_List________| BlockTrans
  |_Cond__|1_0_1|L|___________________Offset______________________| B,BL,BLX
  |_Cond__|1_1_0|P|U|N|W|L|__Rn___|__CRd__|__CP#__|____Offset_____| CoDataTrans
  |_Cond__|1_1_0_0_0_1_0|L|__Rn___|__Rd___|__CP#__|_CPopc_|__CRm__| CoRR ARM9
  |_Cond__|1_1_1_0|_CPopc_|__CRn__|__CRd__|__CP#__|_CP__|0|__CRm__| CoDataOp
  |_Cond__|1_1_1_0|CPopc|L|__CRn__|__Rd___|__CP#__|_CP__|1|__CRm__| CoRegTrans
  |_Cond__|1_1_1_1|_____________Ignored_by_Processor______________| SWI



*/

<|start_filename|>src/memory/Flash.cpp<|end_filename|>
#include "Flash.h"
#include "../util/macros.h"

#include <algorithm> 

#include "assert.h"

void Flash::write(uint32_t address, uint8_t value) {
    if(currMode == WRITE) {

        if(!(address == 0x0E005555 && value == 0xF0)) {
            flash[(address & 0x0000FFFF) + bank] = value;
        }
        currMode = READY;
    } else if(currMode == MEM_BANK) {

        if(address == 0x0E000000 && (value == 0 || value == 1)) {
            bank = value * 0x10000;
        } else {
            DEBUGWARN("currMode error in mem bank command\n");
        }
        currMode = READY;
    } else if(address == 0xE005555 && value == 0xAA) {

        currStage = 0;
    } else if(address == 0xE002AAA && value == 0x55 && currStage == 0) {

        currStage = 1;
    } else if((address == 0xE005555 || is4KbEraseCommand(address, value)) && currStage == 1) {

        // command is being sent
        switch(value) {
            case 0x90: {
                // Enter “Chip identification mode”
                currMode = CHIP_ID;
                temp0x0 = flash[0x0];
                temp0x1 = flash[0x1];
                flash[0x0] = manufacturerId;
                flash[0x1] = deviceId;
                break;
            }
            case 0xF0: {
                // Exit “Chip identification mode”
                currMode = READY;
                flash[0x0] = temp0x0;
                flash[0x1] = temp0x1;
                break;
            }
            case 0x80: {
                // Prepare to receive erase command
                currMode = ERASE;
                break;
            }
            case 0x10: {
                // Erase entire chip
                if(currMode == ERASE) {
                    // TODO: cycle accuracy? (this normally takes some time)
                    flash.fill(0xFF);
                } else {
                    DEBUGWARN("currMode not erase, something wrong in flash!\n");
                }
                break;
            }
            case 0x30: {
                // Erase 4 kilobyte sector
                if(currMode == ERASE) {
                    uint32_t page = (address & 0x0000F000);
                    std::fill_n(flash.begin() + page + bank, 0xFFF, 0xFF);
                } else {    
                    DEBUGWARN("currMode not erase, something wrong in flash!\n");
                }
                break;
            }
            case 0xA0: {
                // Prepare to write single data byte
                currMode = WRITE;
                break;
            }
            case 0xB0: {
                // Set memory bank
                currMode = MEM_BANK;
                break;
            }
        }
    }
}


uint8_t Flash::read(uint32_t address) {
    return flash[(address & 0x0000FFFF) + bank];
}

inline 
bool Flash::is4KbEraseCommand(uint32_t address, uint8_t value) {
    // TODO: correct?
    return (value == 0x30) && !((address & 0xFFFF0FFF) ^ 0x0E000000);
}

void Flash::setSize(uint32_t size) {
    assert(size == 512 || size == 1024);

    if(size == 1024) {
        manufacturerId = 0x62;
        deviceId = 0x13;
    } else {
        // size == 512
        manufacturerId = 0x32;
        deviceId = 0x1B;
    }
}

<|start_filename|>src/arm7tdmi/ARMInstructions/ArmBranchXHandler.h<|end_filename|>

#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"


template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::armBranchXHandler(uint32_t instruction, ARM7TDMI* cpu) {
    assert(((instruction & 0x0FFFFF00) >> 8) == 0b00010010111111111111);
    constexpr uint8_t opcode = op & 0x00F;

    // for this op rn is where rm usually is
    uint8_t rn = cpu->getRm(instruction);
    assert(rn != PC_REGISTER);
    uint32_t rnVal = cpu->getRegister(rn);
    
    if constexpr(opcode == 0x1) {
        // // BX PC=Rn, T=Rn.0
    } else if constexpr(opcode == 0x3) {
        // BLX PC=Rn, T=Rn.0, LR=PC+4
        cpu->setRegister(LINK_REGISTER, cpu->getRegister(PC_REGISTER));
    } else {
        // ?
    }

    /*
        For ARM code, the low bits of the target address should be usually zero,
        otherwise, R15 is forcibly aligned by clearing the lower two bits.
        For THUMB code, the low bit of the target address may/should/must be
        set, the bit is (or is not) interpreted as thumb-bit (depending on the
        opcode), and R15 is then forcibly aligned by clearing the lower bit. In
        short, R15 will be always forcibly aligned, so mis-aligned branches won't
        have effect on subsequent opcodes that use R15, or [R15+disp] as operand.
    */
    bool t = rnVal & 0x1;
    cpu->cpsr.T = t;
    if (t) {
        rnVal &= 0xFFFFFFFE;
    } else {
        rnVal &= 0xFFFFFFFC;
    }
    cpu->setRegister(PC_REGISTER, rnVal);

    return BRANCH;

}

<|start_filename|>src/GameBoyAdvanceImpl.h<|end_filename|>
#pragma once

#include <string>
#include <memory>
#include "Scheduler.h"

class ARM7TDMI;
class Bus;
class LCD;
class PPU;
class DMA;
class Timer;
class Debugger;


class GameBoyAdvanceImpl {
   public:

    GameBoyAdvanceImpl();

    bool loadRom(std::string path);
    void enterMainLoop();
    void printCpuState();

    ARM7TDMI* getCpu();

    static uint64_t cyclesSinceStart;

   private:
    std::shared_ptr<ARM7TDMI> arm7tdmi;
    std::shared_ptr<Bus> bus;
    std::shared_ptr<LCD> screen;
    std::shared_ptr<PPU> ppu;
    std::shared_ptr<DMA> dma;
    std::shared_ptr<Timer> timer;
    std::shared_ptr<Debugger> debugger;
    std::shared_ptr<Scheduler> scheduler;

    uint64_t getTotalCyclesElapsed();
    void testDisplay();

    void dmaXEvent(uint8_t x, Scheduler::Event* dmaEvent, uint16_t currentScanline);

    bool hBlank = false;
    bool scanlineRendered = false;
    bool vBlank = false;

    long previousTime = 0;
    long currentTime = 0;
    long frames = 0;
    double startTimeSeconds = 0.0;
    uint64_t totalCycles= 0;

    bool debugMode = false;

};



<|start_filename|>src/arm7tdmi/ARMInstructions/ArmDataProcHandler.h<|end_filename|>

#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"


template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::armDataProcHandler(uint32_t instruction, ARM7TDMI* cpu) {
    // shift op2
    constexpr bool i = (op & 0x200);
    constexpr bool r = (op & 0x1);
    constexpr uint8_t opcode = ((op & 0x1E0) >> 5);
    constexpr bool s = (op & 0x010);

    uint32_t op2; 
    bool shiftCarryBit;

    // -----SHIFTING------
    if constexpr(i) {
        /*
            The immediate operand rotate field is a 4 bit unsigned integer
            which specifies a shift operation on the 8 bit immediate value.
            This value is zero extended to 32 bits, and then subject to a
            rotate right by twice the value in the rotate field.
        */

        uint32_t imm = instruction & 0x000000FF;
        uint8_t is = (instruction & 0x00000F00) >> 7U;
        op2 = ARM7TDMI::aluShiftRor(imm, is % 32);

        // carry out bit is the least significant discarded bit of rm
        if (is > 0) {
            shiftCarryBit = (imm >> (is - 1)) & 0x1;
        } else {
            shiftCarryBit = cpu->cpsr.C;
        }
    } else {
        /* ~~~~~~~~~ else: shifted register value as 2nd operand ~~~~~~~~~~ */
        uint8_t shiftType = (instruction & 0x00000060) >> 5U;
        uint8_t rmIndex = instruction & 0x0000000F;
        uint32_t rm = cpu->getRegister(rmIndex);
        // see comment in opcode functions for explanation why we're doing this

        if constexpr(!i && r) {
            if(rmIndex == ARM7TDMI::PC_REGISTER) {
                rm += 8;
            }        
        } else {
            if(rmIndex == ARM7TDMI::PC_REGISTER) {
                rm += 4;
            }
        }

        uint32_t shiftAmount;
        bool doShift = true;

        if constexpr(r) {  // register as shift amount
            uint8_t rsIndex = (instruction & 0x00000F00) >> 8U;
            assert(rsIndex != 15);
            shiftAmount = cpu->getRegister(rsIndex) & 0x000000FF;
            if(shiftAmount == 0) {
                op2 = rm;
                shiftCarryBit = cpu->cpsr.C;
                doShift = false;
            }
        } else {  // immediate as shift amount
            shiftAmount = (instruction & 0x00000F80) >> 7U;
        }

        bool immOpIsZero = r ? false : (shiftAmount == 0);
        if(doShift) {
            if (shiftType == 0) {  // Logical Shift Left
                /*
                    A logical shift left (LSL) takes the contents of
                    Rm and moves each bit by the specified amount
                    to a more significant position. The least significant
                    bits of the result are filled with zeros, and the high bits
                    of Rm which do not map into the result are discarded, except
                    that the least significant discarded bit becomes the shifter
                    carry output which may be latched into the C bit of the CPSR
                    when the ALU operation is in the logical class
                */
                if (!immOpIsZero) {
                    op2 = ARM7TDMI::aluShiftLsl(rm, shiftAmount);
                    shiftCarryBit = (shiftAmount > 32) ? 0 : ((rm >> (32 - shiftAmount)) & 1);
                } else {  // no op performed, carry flag stays the same
                    op2 = rm;
                    shiftCarryBit = cpu->cpsr.C;
                }
            } else if (shiftType == 1) {  // Logical Shift Right
                /*
                    A logical shift right (LSR) is similar, but the contents
                    of Rm are moved to less significant positions in the result
                */
                if (!immOpIsZero) {
                    op2 = ARM7TDMI::aluShiftLsr(rm, shiftAmount);
                    shiftCarryBit = (shiftAmount > 32) ? 0 : ((rm >> (shiftAmount - 1)) & 1);
                } else {
                    /*
                        The form of the shift field which might be expected to
                        correspond to LSR #0 is used to encode LSR #32, which has a
                        zero result with bit 31 of Rm as the carry output
                    */
                    op2 = 0;
                    shiftCarryBit = rm >> 31;
                }
            } else if (shiftType == 2) {  // Arithmetic Shift Right
                /*
                    An arithmetic shift right (ASR) is similar to logical shift right,
                    except that the high bits are filled with bit 31 of Rm instead of
                zeros. This preserves the sign in 2's complement notation
                */
                if (!immOpIsZero) {
                    op2 = ARM7TDMI::aluShiftAsr(rm, shiftAmount);
                    shiftCarryBit = (shiftAmount >= 32) ? (rm & 0x80000000) : ((rm >> (shiftAmount - 1)) & 1);
                } else {
                    /*
                        The form of the shift field which might be expected to give ASR
                    #0 is used to encode ASR #32. Bit 31 of Rm is again used as the
                    carry output, and each bit of operand 2 is also equal to bit 31
                    of Rm.
                    */
                    op2 = ARM7TDMI::aluShiftAsr(rm, 32);
                    shiftCarryBit = rm >> 31;
                }
            } else {  // Rotating Shift
                /*
                    Rotate right (ROR) operations reuse the bits which “overshoot”
                    in a logical shift right operation by reintroducing them at the
                    high end of the result, in place of the zeros used to fill the high
                    end in logical right operation
                */
                if (!immOpIsZero) {
                    op2 = ARM7TDMI::aluShiftRor(rm, shiftAmount % 32);
                    shiftCarryBit = (rm >> ((shiftAmount % 32) - 1)) & 1;
                } else {
                    /*
                        The form of the shift field which might be expected to give ROR
                    #0 is used to encode a special function of the barrel shifter,
                        rotate right extended (RRX). This is a rotate right by one bit
                    position of the 33 bit quantity formed by appending the CPSR C
                    flag to the most significant end of the contents of Rm as shown
                    */
                    op2 = rm >> 1;
                    op2 = op2 | (((uint32_t)(cpu->cpsr.C)) << 31);
                    shiftCarryBit = rm & 1;
                }
            }
        }
    }

    uint8_t rd = getRd(instruction);
    uint8_t rn = getRn(instruction);
    //uint8_t opcode = getOpcode(instruction);
    bool carryBit = (cpu->cpsr).C;
    bool overflowBit = (cpu->cpsr).V;
    bool signBit = (cpu->cpsr).N;
    bool zeroBit = (cpu->cpsr).Z;

    uint32_t rnVal;
    // if rn == pc regiser, have to add to it to account for pipelining /
    // prefetching
    // TODO probably dont need this logic if pipelining is emulated
    if constexpr(!i && r) {
        if (rn != ARM7TDMI::PC_REGISTER) {
            rnVal = cpu->getRegister(rn);
        } else {
            rnVal = cpu->getRegister(rn) + 8;
        }
    } else {
        if (rn != ARM7TDMI::PC_REGISTER) {
            rnVal = cpu->getRegister(rn);
        } else {
            rnVal = cpu->getRegister(rn) + 4;
        }
    }

    if constexpr(opcode == 0x0) {  // AND
        uint32_t result = rnVal & op2;
        cpu->setRegister(rd, result);
        zeroBit = ARM7TDMI::aluSetsZeroBit(result);
        signBit = ARM7TDMI::aluSetsSignBit(result);
        carryBit = shiftCarryBit;
    } else if constexpr(opcode == 0x1) {  // EOR
        uint32_t result = rnVal ^ op2;
        cpu->setRegister(rd, result);
        zeroBit = ARM7TDMI::aluSetsZeroBit(result);
        signBit = ARM7TDMI::aluSetsSignBit(result);
        carryBit = shiftCarryBit;
    } else if constexpr(opcode == 0x2) {  // SUB
        uint32_t result = rnVal - op2;
        cpu->setRegister(rd, result);
        zeroBit = ARM7TDMI::aluSetsZeroBit(result);
        signBit = ARM7TDMI::aluSetsSignBit(result);
        carryBit = ARM7TDMI::aluSubtractSetsCarryBit(rnVal, op2);
        overflowBit = ARM7TDMI::aluSubtractSetsOverflowBit(rnVal, op2, result);
    } else if constexpr(opcode == 0x3) {  // RSB
        uint32_t result = op2 - rnVal;
        cpu->setRegister(rd, result);
        zeroBit = ARM7TDMI::aluSetsZeroBit(result);
        signBit = ARM7TDMI::aluSetsSignBit(result);
        carryBit = ARM7TDMI::aluSubtractSetsCarryBit(op2, rnVal);
        overflowBit = ARM7TDMI::aluSubtractSetsOverflowBit(op2, rnVal, result);
    } else if constexpr(opcode == 0x4) {  // ADD
        uint32_t result = rnVal + op2;
        cpu->setRegister(rd, result);
        zeroBit = ARM7TDMI::aluSetsZeroBit(result);
        signBit = ARM7TDMI::aluSetsSignBit(result);
        carryBit = ARM7TDMI::aluAddSetsCarryBit(rnVal, op2);
        overflowBit = ARM7TDMI::aluAddSetsOverflowBit(rnVal, op2, result);
    } else if constexpr(opcode == 0x5) {  // ADC
        uint64_t result = (uint64_t)rnVal + (uint64_t)op2 + (uint64_t)(cpu->cpsr).C;
        cpu->setRegister(rd, (uint32_t)result);
        zeroBit = ARM7TDMI::aluSetsZeroBit((uint32_t)result);
        signBit = ARM7TDMI::aluSetsSignBit((uint32_t)result);
        carryBit = ARM7TDMI::aluAddWithCarrySetsCarryBit(result);
        overflowBit = ARM7TDMI::aluAddWithCarrySetsOverflowBit(rnVal, op2, (uint32_t)result, cpu);
    } else if constexpr(opcode == 0x6) {  // SBC
        uint64_t result = (uint64_t)rnVal + ~((uint64_t)op2) + (uint64_t)(cpu->cpsr.C);
        cpu->setRegister(rd, (uint32_t)result);
        zeroBit = ARM7TDMI::aluSetsZeroBit((uint32_t)result);
        signBit = ARM7TDMI::aluSetsSignBit((uint32_t)result);
        carryBit = ARM7TDMI::aluSubWithCarrySetsCarryBit(result);
        overflowBit = ARM7TDMI::aluSubWithCarrySetsOverflowBit(rnVal, op2, (uint32_t)result, cpu);
    } else if constexpr(opcode == 0x7) {  // RSC
        uint64_t result = (uint64_t)op2 + ~((uint64_t)rnVal) + (uint64_t)(cpu->cpsr.C);
        cpu->setRegister(rd, (uint32_t)result);
        zeroBit = ARM7TDMI::aluSetsZeroBit((uint32_t)result);
        signBit = ARM7TDMI::aluSetsSignBit((uint32_t)result);
        carryBit = ARM7TDMI::aluSubWithCarrySetsCarryBit(result);
        overflowBit = ARM7TDMI::aluSubWithCarrySetsOverflowBit(op2, rnVal, (uint32_t)result, cpu);
    } else if constexpr(opcode == 0x8) {  // TST
        uint32_t result = rnVal & op2;
        carryBit = shiftCarryBit;
        zeroBit = ARM7TDMI::aluSetsZeroBit(result);
        signBit = ARM7TDMI::aluSetsSignBit(result);
    } else if constexpr(opcode == 0x9) {  // TEQ
        uint32_t result = rnVal ^ op2;
        carryBit = shiftCarryBit;
        zeroBit = ARM7TDMI::aluSetsZeroBit(result);
        signBit = ARM7TDMI::aluSetsSignBit(result);
    } else if constexpr(opcode == 0xA) {  // CMP
        uint32_t result = rnVal - op2;
        zeroBit = ARM7TDMI::aluSetsZeroBit(result);
        signBit = ARM7TDMI::aluSetsSignBit(result);
        carryBit = ARM7TDMI::aluSubtractSetsCarryBit(rnVal, op2);
        overflowBit = ARM7TDMI::aluSubtractSetsOverflowBit(rnVal, op2, result);
    } else if constexpr(opcode == 0xB) {  // CMN
        uint32_t result = rnVal + op2;
        zeroBit = ARM7TDMI::aluSetsZeroBit(result);
        signBit = ARM7TDMI::aluSetsSignBit(result);
        carryBit = ARM7TDMI::aluAddSetsCarryBit(rnVal, op2);
        overflowBit = ARM7TDMI::aluAddSetsOverflowBit(rnVal, op2, result);
    } else if constexpr(opcode == 0xC) {  // ORR
        uint32_t result = rnVal | op2;
        cpu->setRegister(rd, result);
        zeroBit = ARM7TDMI::aluSetsZeroBit(result);
        signBit = ARM7TDMI::aluSetsSignBit(result);
        carryBit = shiftCarryBit;
    } else if constexpr(opcode == 0xD) {  // MOV
        uint32_t result = op2;
        cpu->setRegister(rd, result);
        zeroBit = ARM7TDMI::aluSetsZeroBit(result);
        carryBit = shiftCarryBit;
        signBit = ARM7TDMI::aluSetsSignBit(result);
    } else if constexpr(opcode == 0xE) {  // BIC
        uint32_t result = rnVal & (~op2);
        cpu->setRegister(rd, result);
        zeroBit = ARM7TDMI::aluSetsZeroBit(result);
        signBit = ARM7TDMI::aluSetsSignBit(result);
        carryBit = shiftCarryBit;
    } else {  // MVN
        uint32_t result = ~op2;
        cpu->setRegister(rd, result);
        zeroBit = ARM7TDMI::aluSetsZeroBit(result);
        signBit = ARM7TDMI::aluSetsSignBit(result);
        carryBit = shiftCarryBit;
    }

    if constexpr(s) {
        if(rd != ARM7TDMI::PC_REGISTER) {
            cpu->cpsr.Z = zeroBit;
            cpu->cpsr.C = carryBit;
            cpu->cpsr.N = signBit;
            cpu->cpsr.V = overflowBit;
        } else {
            cpu->cpsr = *(cpu->getCurrentModeSpsr());
            cpu->switchToMode(ARM7TDMI::Mode((*(cpu->getCurrentModeSpsr())).Mode));
        }
    }

    ARM7TDMI::Cycles cycles = {.nonSequentialCycles = 0,
                     .sequentialCycles = 1,
                     .internalCycles = 0,
                     .waitState = 0};
    
    // TODO: can potentially optimize a bit by using already existing condition checks earlier in code

    if constexpr (!i && r) {
        cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
        if(rd == ARM7TDMI::PC_REGISTER) {
            return ARM7TDMI::FetchPCMemoryAccess::BRANCH;
        } else {
            return ARM7TDMI::FetchPCMemoryAccess::NONSEQUENTIAL;
        }
    }
    if(rd == ARM7TDMI::PC_REGISTER) {
        return ARM7TDMI::FetchPCMemoryAccess::BRANCH;;
    } else {
        return ARM7TDMI::FetchPCMemoryAccess::SEQUENTIAL;
    }

}

<|start_filename|>src/arm7tdmi/ARMInstructions/ArmPsrHandler.h<|end_filename|>

#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::armPsrHandler(uint32_t instruction, ARM7TDMI* cpu) {
    assert(!(instruction & 0x0C000000));
    //assert(!sFlagSet(instruction));
    // bit 25: I - Immediate Operand Flag
    // (0=Register, 1=Immediate) (Zero for MRS)
    constexpr bool immediate = (op & 0x200);
    // bit 22: Psr - Source/Destination PSR
    // (0=CPSR, 1=SPSR_<current mode>)
    constexpr bool psrSource = (op & 0x040);
    constexpr bool opcode = (op & 0x020);

    // bit 21: special opcode for PSR
    if constexpr(opcode) {
        assert((instruction & 0x0000F000) == 0x0000F000);
        uint8_t fscx = (instruction & 0x000F0000) >> 16;
        ARM7TDMI::ProgramStatusRegister *psr = (!psrSource ? &(cpu->cpsr) : cpu->getCurrentModeSpsr());
        if constexpr (immediate) {
            uint32_t immValue = (uint32_t)(instruction & 0x000000FF);
            uint8_t shift = (instruction & 0x00000F00) >> 7;
            cpu->transferToPsr(ARM7TDMI::aluShiftRor(immValue, shift), fscx, psr);
        } else {  // register
            assert(!(instruction & 0x00000FF0));
            assert(ARM7TDMI::getRm(instruction) != ARM7TDMI::PC_REGISTER);
            // TODO: refactor this, don't have to pass in a pointer to the psr
            cpu->transferToPsr(cpu->getRegister(ARM7TDMI::getRm(instruction)), fscx, psr);
        }
    } else {
        assert(!immediate);
        assert(ARM7TDMI::getRn(instruction) == 0xF);
        assert(!(instruction & 0x00000FFF));
        uint8_t rd = ARM7TDMI::getRd(instruction);
        if constexpr (!psrSource) {
            // TODO: this psrToInt and psr stuff maybe can be optimized, it's messy
            cpu->setRegister(rd, ARM7TDMI::psrToInt(cpu->cpsr));
        } else {
            cpu->setRegister(rd, ARM7TDMI::psrToInt(*(cpu->getCurrentModeSpsr())));
        }       
    }

    return ARM7TDMI::FetchPCMemoryAccess::SEQUENTIAL;
}


<|start_filename|>src/util/macros.h<|end_filename|>
#pragma once

#include <iostream>

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

#define NDEBUG 1;
//#define NDEBUGWARN 1;


#ifdef NDEBUG
#define DEBUG(x)
#else
#define DEBUG(x)        \
    do {                \
        std::cout << "INFO: " << x; \
    } while (0)
#endif


#ifdef NDEBUGWARN
#define DEBUGWARN(x)
#else
#define DEBUGWARN(x)        \
    do {                \
        std::cout << "WARN: " << x; \
    } while (0)
#endif


<|start_filename|>src/Timer.cpp<|end_filename|>
#include "Timer.h"
#include "memory/Bus.h"
#include "arm7tdmi/ARM7TDMI.h"
#include "Scheduler.h"
#include "GameBoyAdvanceImpl.h"


/*
    update IORegister state in bus because about to read from the timer
*/
uint8_t Timer::updateBusToPrepareForTimerRead(uint32_t address, uint8_t width) {
    // manually preparing the memory so that what's read will be up to date
    switch(address) {
        case 0x4000100: {
            calculateTimerXCounter(0, GameBoyAdvanceImpl::cyclesSinceStart);
            bus->iORegisters[Bus::IORegister::TM0CNT_L] = timerCounter[0]; 
            bus->iORegisters[Bus::IORegister::TM0CNT_L + 1] = (timerCounter[0]) >> 8; 
            break;
        }
        case 0x4000104: {
            calculateTimerXCounter(1, GameBoyAdvanceImpl::cyclesSinceStart);
            bus->iORegisters[Bus::IORegister::TM1CNT_L] = timerCounter[1]; 
            bus->iORegisters[Bus::IORegister::TM1CNT_L + 1] = (timerCounter[1]) >> 8; 
            break;
        }
        case 0x4000108: {
            calculateTimerXCounter(2, GameBoyAdvanceImpl::cyclesSinceStart);
            bus->iORegisters[Bus::IORegister::TM2CNT_L] = timerCounter[2]; 
            bus->iORegisters[Bus::IORegister::TM2CNT_L + 1] = (timerCounter[2]) >> 8; 
            break;
        }
        case 0x400010C: {
            calculateTimerXCounter(3, GameBoyAdvanceImpl::cyclesSinceStart);
            bus->iORegisters[Bus::IORegister::TM3CNT_L] = timerCounter[3]; 
            bus->iORegisters[Bus::IORegister::TM3CNT_L + 1] = (timerCounter[3]) >> 8; 
            break;
        } 
        default: {
            break;
        }
    }
    return 0;
}

/*
    update Timer state upon write
*/
void Timer::updateTimerUponWrite(uint32_t address, uint32_t value, uint8_t width) {
    while(width != 0) {
        uint8_t byte = value & 0xFF;

        switch(address) {
            case 0x4000100: {
                setTimerXReloadLo(byte, 0);
                break;
            }
            case 0x4000101: {
                setTimerXReloadHi(byte, 0);
                break;
            }
            case 0x4000102: {
                setTimerXControlLo(byte, 0);
                break;
            }
            case 0x4000103: {
                setTimerXControlHi(byte, 0);
                break;
            }
            case 0x4000104: {
                setTimerXReloadLo(byte, 1);
                break;
            }
            case 0x4000105: {
                setTimerXReloadHi(byte, 1);
                break;
            }
            case 0x4000106: {
                setTimerXControlLo(byte, 1);
                break;
            }
            case 0x4000107: {
                setTimerXControlHi(byte, 1);
                break;
            }            
            case 0x4000108: {
                setTimerXReloadLo(byte, 2);
                break;
            }
            case 0x4000109: {
                setTimerXReloadHi(byte, 2);
                break;
            }
            case 0x400010A: {
                setTimerXControlLo(byte, 2);
                break;
            }
            case 0x400010B: {
                setTimerXControlHi(byte, 2);
                break;
            }
            case 0x400010C: {
                setTimerXReloadLo(byte, 3);
                break;
            }
            case 0x400010D: {
                setTimerXReloadHi(byte, 3);
                break;
            }
            case 0x400010E: {
                setTimerXControlLo(byte, 3);
                break;
            }
            case 0x400010F: {
                setTimerXControlHi(byte, 3);
                break;
            }
            default: {
                break;
            }        
        }

        width -= 8;
        address += 1;
        value = value >> 8;
    }
    return;
}

inline
void Timer::setTimerXControlHi(uint8_t val, uint8_t x) {
}

inline
void Timer::setTimerXControlLo(uint8_t val, uint8_t x) {
    Scheduler::EventType timerEvent;
    switch(x) {
        case 0: { timerEvent = Scheduler::EventType::TIMER0; break; }
        case 1: { timerEvent = Scheduler::EventType::TIMER1; break; }
        case 2: { timerEvent = Scheduler::EventType::TIMER2; break; }
        case 3: { timerEvent = Scheduler::EventType::TIMER3; break; }
        default: { break; }
    }

    // remove old event
    scheduler->removeEvent(timerEvent);

    uint8_t prescalerSelection = val & 0x3;
    switch(prescalerSelection) {
        case 0: { timerPrescaler[x] = 1; break; }
        case 1: { timerPrescaler[x] = 64; break; }
        case 2: { timerPrescaler[x] = 256; break; }
        case 3: { timerPrescaler[x] = 1024; break; }
        default: { break; }
    }

    if(!timerStart[x] && (val & 0x80)) {
        // reload value is copied into the counter when the timer start bit becomes changed from 0 to 1.
        timerCounter[x] = timerReload[x];
    }

    uint64_t cyclesPassed = GameBoyAdvanceImpl::cyclesSinceStart;

    // update counter
    calculateTimerXCounter(x, cyclesPassed);

    timerCountUp[x] = val & 0x4;
    timerIrqEnable[x] = val & 0x40;
    timerStart[x] = val & 0x80;

    if((val & 0x80) && !(val & 0x4)) {
        // only schedule if the timer is not count-up (since count up timers will automatically overflow)
        // timer enabled, so schedule new event
        if(timerCounter[x] > 0xFFFF) { // if overflow
            DEBUGWARN("timer overflowed outside of scheduled event!\n");
            // schedule timer to run immediately
            scheduler->addEvent(timerEvent, 0, Scheduler::EventCondition::NULL_CONDITION, false);
        } else {
            // add event at time when timer will go off
            scheduler->addEvent(timerEvent, 
                                (0x10000 - timerCounter[x]) * timerPrescaler[x], 
                                Scheduler::EventCondition::NULL_CONDITION,
                                false);
        }
    }
}

inline
void Timer::setTimerXReloadHi(uint8_t val, uint8_t x) {
    timerReload[x] = (timerReload[x] & 0x00FF) | ((uint16_t)val << 8); 
}

inline
void Timer::setTimerXReloadLo(uint8_t val, uint8_t x) {
    timerReload[x] = (timerReload[x] & 0xFF00) | (uint16_t)val; 
}

void Timer::connectBus(std::shared_ptr<Bus> bus) {
    this->bus = bus;
}

void Timer::connectCpu(std::shared_ptr<ARM7TDMI> cpu) {
    this->cpu = cpu;
}

void Timer::connectScheduler(std::shared_ptr<Scheduler> scheduler) {
    this->scheduler = scheduler;
}


void Timer::timerXOverflowEvent(uint8_t x) {
    calculateTimerXCounter(x, GameBoyAdvanceImpl::cyclesSinceStart);
    if(timerCounter[x] <= 0xFFFF) {
        DEBUGWARN("timer didn't overflow! scheduling error\n");
        scheduler->printEventList();
        return;
    }

    if(timerIrqEnable[x]) {
        queueTimerInterrupt(x);
    }

    timerCounter[x] = timerReload[x];
    uint8_t cascadeX = x + 1;
    bool overflow = true;
    while(overflow && cascadeX <= 3 && timerCountUp[cascadeX] && timerStart[cascadeX]) {
        timerCounter[cascadeX] += 1;
        if(timerCounter[cascadeX] > 0xFFFF) {
            if(timerIrqEnable[cascadeX]) {
                queueTimerInterrupt(cascadeX);
            }
            timerCounter[cascadeX] = timerReload[cascadeX];
        } else {
            overflow = false;
        }
        cascadeX++;
    }
    Scheduler::EventType timerEvent;
    switch(x) {
        case 0: { timerEvent = Scheduler::EventType::TIMER0; break; }
        case 1: { timerEvent = Scheduler::EventType::TIMER1; break; }
        case 2: { timerEvent = Scheduler::EventType::TIMER2; break; }
        case 3: { timerEvent = Scheduler::EventType::TIMER3; break; }
        default: { break; }
    }
    scheduler->addEvent(timerEvent,
                       (0x10000 - timerCounter[x]) * timerPrescaler[x], 
                        Scheduler::EventCondition::NULL_CONDITION, 
                        false);
}

inline
void Timer::queueTimerInterrupt(uint8_t x) {
    switch(x) {
        case 0: {
            cpu->queueInterrupt(ARM7TDMI::Interrupt::Timer0Overflow);
            break;
        }
        case 1: {
            cpu->queueInterrupt(ARM7TDMI::Interrupt::Timer1Overflow);
            break;
        }
        case 2: {
            cpu->queueInterrupt(ARM7TDMI::Interrupt::Timer2Overflow);
            break;
        }
        case 3: {
            cpu->queueInterrupt(ARM7TDMI::Interrupt::Timer3Overflow);
            break;
        }
        default: {
            break;
        }
    }
}

inline
void Timer::calculateTimerXCounter(uint8_t x, uint64_t cyclesPassed) {
    // update counter
    if(!timerCountUp[x]) {
        if(timerStart[x]) {
            uint32_t increments = ((cyclesPassed - timerCycleOfLastUpdate[x]) + timerExcessCycles[x]) / timerPrescaler[x];

            if(increments != 0) {
                timerExcessCycles[x] = ((cyclesPassed - timerCycleOfLastUpdate[x]) + timerExcessCycles[x]) % (timerPrescaler[x]);
            } else {
                timerExcessCycles[x] += (cyclesPassed - timerCycleOfLastUpdate[x]);
            }
            
            timerCounter[x] += increments; 
        }
        timerCycleOfLastUpdate[x] = cyclesPassed;
    }
}     


<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbBHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbBHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xF800) == 0xE000);
    uint32_t offset = signExtend12Bit((instruction & 0x07FF) << 1);
    cpu->setRegister(PC_REGISTER, (cpu->getRegister(PC_REGISTER) + 2 + offset) & 0xFFFFFFFE);
    return BRANCH; 
}


<|start_filename|>src/arm7tdmi/ARMInstructions/ArmSdtHandler.h<|end_filename|>

#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"


template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::armSdtHandler(uint32_t instruction, ARM7TDMI* cpu) {
    constexpr uint8_t opcode = (op & 0x006) >> 1;
    constexpr bool p = op & 0x100;
    constexpr bool u = op & 0x080;
    constexpr bool b = op & 0x040;
    constexpr bool l = op & 0x010;
    constexpr bool w = op & 0x020;
    constexpr bool i = op & 0x200;

    // TODO:  implement the following restriction <expression>  ;an immediate
    // used as address
    // ;*** restriction: must be located in range PC+/-4095+8, if so,
    // ;*** assembler will calculate offset and use PC (R15) as base.
    assert((instruction & 0x0C000000) == (instruction & 0x04000000));
    uint8_t rd = getRd(instruction);
    uint32_t rdVal = (rd == 15) ? cpu->getRegister(rd) + 8 : cpu->getRegister(rd);
    uint8_t rn = getRn(instruction);
    uint32_t rnVal = (rn == 15) ? cpu->getRegister(rn) + 4 : cpu->getRegister(rn);

    uint32_t offset;
    // I - Immediate Offset Flag (0=Immediate, 1=Shifted Register)
    if constexpr(i) {
        // Register shifted by Immediate as Offset
        assert(!(instruction & 0x00000010));  // bit 4 Must be 0 (Reserved, see
                                              // The Undefined Instruction)
        uint8_t rm = getRm(instruction);
        assert(rm != 15);
        uint8_t shiftAmount = (instruction & 0x00000F80) >> 7;

        if constexpr(opcode == 0) {
            offset = shiftAmount != 0
                            ? aluShiftLsl(cpu->getRegister(rm), shiftAmount)
                            : cpu->getRegister(rm);
        } else if constexpr(opcode == 1) {
            offset = shiftAmount != 0
                            ? aluShiftLsr(cpu->getRegister(rm), shiftAmount)
                            : 0;

        } else if constexpr(opcode == 2) {
            offset = shiftAmount != 0
                            ? aluShiftAsr(cpu->getRegister(rm), shiftAmount)
                            : aluShiftAsr(cpu->getRegister(rm), 32);
        } else {
            // opcode == 3
            offset = (shiftAmount != 0
                ? aluShiftRor(cpu->getRegister(rm), shiftAmount % 32)
                : aluShiftRrx(cpu->getRegister(rm), 1, cpu));
        }
    } else {  // immediate as offset (12 bit offset)
        offset = instruction & 0x00000FFF;
    }
    uint32_t address = rnVal;

    // U - Up/Down Bit (0=down; subtract offset
    // from base, 1=up; add to base)

    // P - Pre/Post (0=post; add offset after
    // transfer, 1=pre; before trans.)

    if constexpr(p) {  // add offset before transfer
        if constexpr(u) {
            address = address + offset;
        } else {    
            address = address - offset;
        }
        if constexpr(w) {
            // write address back into base register
            cpu->setRegister(rn, address);
        }
    } else {
        // add offset after transfer and always write back
        if constexpr(u) {
            cpu->setRegister(rn, address + offset);
        } else {    
            cpu->setRegister(rn, address - offset);
        }
    }

    // B - Byte/Word bit (0=transfer
    // 32bit/word, 1=transfer 8bit/byte)
    // TODO implement t bit, force non-privilege access
    // L - Load/Store bit (0=Store to memory, 1=Load from memory)
    if constexpr(l) {
        // LDR{cond}{B}{T} Rd,<Address> ;Rd=[Rn+/-<offset>]
        if constexpr(b) {  // transfer 8 bits
            cpu->setRegister(rd, (uint32_t)(cpu->bus->read8(address, Bus::CycleType::NONSEQUENTIAL)));
        } else {  // transfer 32 bits
            // aligned to word
            // Reads from forcibly aligned address "addr AND (NOT 3)",
            // and does then rotate the data as "ROR (addr AND 3)*8". T
            // TODO: move the masking and shifting into the read/write functions
            cpu->setRegister(rd, aluShiftRor(cpu->bus->read32(address,
                                Bus::CycleType::NONSEQUENTIAL),
                                (address & 3) * 8));
        }
        cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
        if(rd == PC_REGISTER) {
            return BRANCH;
        } else {
            return NONSEQUENTIAL;
        }
    } else {
        // STR{cond}{B}{T} Rd,<Address>   ;[Rn+/-<offset>]=Rd
        if constexpr(b) {  // transfer 8 bits
            cpu->bus->write8(address, (uint8_t)(rdVal), 
                             Bus::CycleType::NONSEQUENTIAL);
        } else {  // transfer 32 bits
            cpu->bus->write32(address, (rdVal), 
                              Bus::CycleType::NONSEQUENTIAL);
        }
        
        return NONSEQUENTIAL;
    }
}

<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbCondBHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

// op = instruction & 0xFFC0;
// anything within the mask can be constexpr!

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbCondBHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xF000) == 0xD000);
    DEBUG("in THUMB.16: conditional branch\n");
    //uint8_t opcode = (instruction & 0x0F00) >> 8;
    constexpr uint8_t opcode = (op & 0x03C) >> 2;
    uint32_t offset = signExtend9Bit((instruction & 0x00FF) << 1);
    bool jump = false;

    DEBUG("opcode: " << (uint64_t)opcode << "\n");

    // Destination address must by halfword aligned (ie. bit 0 cleared)
    // Return: No flags affected, PC adjusted if condition true

    if constexpr(opcode == 0x0) {
        // 0: BEQ label        ;Z=1         ;equal (zero) (same)
        jump = cpu->cpsr.Z;
    } else if constexpr(opcode == 0x1) {
        // 1: BNE label        ;Z=0         ;not equal (nonzero) (not same)
        jump = !(cpu->cpsr.Z);
    } else if constexpr(opcode == 0x2) {
        // 2: BCS/BHS label    ;C=1         ;unsigned higher or same (carry set)
        jump = cpu->cpsr.C;
    } else if constexpr(opcode == 0x3) {
        // 3: BCC/BLO label    ;C=0         ;unsigned lower (carry cleared)
        jump = !(cpu->cpsr.C);
    } else if constexpr(opcode == 0x4) {
        // 4: BMI label        ;N=1         ;signed  negative (minus)
        jump = cpu->cpsr.N;
    } else if constexpr(opcode == 0x5) {
        // 5: BPL label        ;N=0         ;signed  positive or zero (plus)
        jump = !(cpu->cpsr.N);
    } else if constexpr(opcode == 0x6) {
        // 6: BVS label        ;V=1         ;signed  overflow (V set)
        jump = cpu->cpsr.V;
    } else if constexpr(opcode == 0x7) {
        // 7: BVC label        ;V=0         ;signed  no overflow (V cleared)
        jump = !(cpu->cpsr.V);
    } else if constexpr(opcode == 0x8) {
        // 8: BHI label        ;C=1 and Z=0 ;unsigned higher
        jump = !(cpu->cpsr.Z) && (cpu->cpsr.C);
    } else if constexpr(opcode == 0x9) {
        // 9: BLS label        ;C=0 or Z=1  ;unsigned lower or same
        jump = !(cpu->cpsr.C) || cpu->cpsr.Z;
    } else if constexpr(opcode == 0xA) {
        // A: BGE label        ;N=V         ;signed greater or equal
        jump = cpu->cpsr.N == cpu->cpsr.V;
    } else if constexpr(opcode == 0xB) {
        // B: BLT label        ;N<>V        ;signed less than
        jump = cpu->cpsr.N != cpu->cpsr.V;
    } else if constexpr(opcode == 0xC) {
        // C: BGT label        ;Z=0 and N=V ;signed greater than
        jump = !(cpu->cpsr.Z) && (cpu->cpsr.N == cpu->cpsr.V);
    } else if constexpr(opcode == 0xD) {
        // D: BLE label        ;Z=1 or N<>V ;signed less or equal
        jump = (cpu->cpsr.Z) || (cpu->cpsr.N != cpu->cpsr.V);
    } else if constexpr(opcode == 0xE) {
        // E: Undefined, should not be used
        assert(false);
    } else {
        // opcode == 0xF
        //  F: Reserved for SWI instruction (see SWI opcode)
        assert(false);
    }

    if(jump) {
        cpu->setRegister(PC_REGISTER, (cpu->getRegister(PC_REGISTER) + 2 + offset) & 0xFFFFFFFE);
        return BRANCH;
    } else {
        return SEQUENTIAL;
    }
}


<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbLdPcRelHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbLdPcRelHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xF800) == 0x4800);
    uint16_t offset = (instruction & 0x00FF) << 2;
    // uint8_t rd = (instruction & 0x0700) >> 8;
    //01 1100
    constexpr uint8_t rd = (op & 0x01C) >> 2;

    uint32_t address = ((cpu->getRegister(PC_REGISTER) + 2) & 0xFFFFFFFD) + offset;
    uint32_t value = aluShiftRor(cpu->bus->read32(address, 
                                 Bus::CycleType::NONSEQUENTIAL), 
                                 (address & 3) * 8);
                                 
    cpu->setRegister(rd, value);
    cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
    return NONSEQUENTIAL;
}


<|start_filename|>src/gba.cpp<|end_filename|>
#include "../include/GameBoyAdvance.hpp"
#include <execinfo.h>
#include <signal.h>
#include <unistd.h>
#include <iostream>

GameBoyAdvance gba;

void handler(int sig) {
    void *array[50];
    size_t size;

    // get void*'s for all entries on the stack
    size = backtrace(array, 50);
    printf("~~~~~~~~~~~~~~~~~~ Uh oh! Error! Printing final CPU state ~~~~~~~~~~~~~~~~~~\n");

    gba.printCpuState();

    // print out all the frames to stderr
    fprintf(stderr, "Error: signal %d:\n", sig);
    backtrace_symbols_fd(array, size, STDERR_FILENO);
    exit(1);
}

int main(int argc, char *argv[]) {
    signal(SIGABRT, handler);
    signal(SIGTRAP, handler);
    signal(SIGSEGV, handler);
    bool success = true;
    if(argc < 2) {
        std::cerr << "Please include path to a GBA ROM" << std::endl;
        success = false;
    } else {
        if(gba.loadRom(argv[1])) {
            gba.runRom();
        }
        else {
            success = false;
        }
    }
    return success;
}



<|start_filename|>src/arm7tdmi/ARMInstructions/ArmSwiHandler.h<|end_filename|>

#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"


template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::armSwiHandler(uint32_t instruction, ARM7TDMI* cpu) {
    constexpr uint16_t opcode = (op & 0xF00);
    if constexpr(opcode == 0xF00) {
        // 1111b: SWI{cond} nn   ;software interrupt
        cpu->switchToMode(Mode::SUPERVISOR);
        // switch to ARM mode, interrupts disabled
        *(cpu->currentSpsr) = cpu->cpsr;
        cpu->cpsr.Mode = Mode::SUPERVISOR;
        cpu->cpsr.T = 0;
        cpu->cpsr.I = 1; 
        cpu->setRegister(LINK_REGISTER, cpu->getRegister(PC_REGISTER));
        cpu->setRegister(PC_REGISTER, 0x8);
    } else if constexpr(opcode == 0x100) {
        // 0001b: BKPT      nn   ;breakpoint (ARMv5 and up)
        DEBUGWARN("BKPT instruction not implemented!\n");
    } else {
        assert(false);
    }

    return BRANCH;

}

<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbLdStSpRelHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

// op = instruction & 0xFFC0;
// anything within the mask can be constexpr!

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbLdStSpRelHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xF000) == 0x9000);
    //uint8_t opcode = (instruction & 0x0800) >> 11;
    constexpr bool opcode = (op & 0x020);
    //uint8_t rd = (instruction & 0x0700) >> 8;
    constexpr uint8_t rd = (op & 0x01C) >> 2;
    uint16_t offset = (instruction & 0x00FF) << 2;
    uint32_t address = cpu->getRegister(SP_REGISTER) + offset;

    if constexpr(!opcode) {
        // 0: STR  Rd,[SP,#nn]  ;store 32bit data   WORD[SP+nn] = Rd
        cpu->bus->write32(address, cpu->getRegister(rd), Bus::CycleType::NONSEQUENTIAL); 
    } else {
        // 1: LDR  Rd,[SP,#nn]  ;load  32bit data   Rd = WORD[SP+nn]
        uint32_t value = aluShiftRor(cpu->bus->read32(address, 
                                        Bus::CycleType::NONSEQUENTIAL),
                                        (address & 3) * 8);
        cpu->setRegister(rd, value);
        cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
    }

    return NONSEQUENTIAL;
}



<|start_filename|>src/DMA.cpp<|end_filename|>
#include "DMA.h"
#include "arm7tdmi/ARM7TDMI.h"
#include "memory/Bus.h"
#include "GameBoyAdvanceImpl.h"
#include "PPU.h"
#include "Scheduler.h"
#include "assert.h"
#include "memory/EEPROM.h"


// TODO: DMA specs not fully implemented yet
// TODO: fix mgba suite ROM Load DMA0 tests, which fail
uint32_t DMA::dmaX(uint8_t x, bool vBlank, bool hBlank, uint16_t scanline) {
    // TODO: optimization of this....
    // TODO: The 'Special' setting (Start Timing=3) depends on the DMA channel:DMA0=Prohibited, DMA1/DMA2=Sound FIFO, DMA3=Video Capture
    uint32_t ioRegOffset =  0xC * x;

    // 40000BAh - DMA0CNT_H - DMA 0 Control (R/W)
    uint16_t control = (uint16_t)(bus->iORegisters[Bus::IORegister::DMA0CNT_H + ioRegOffset]) |
                       (uint16_t)(bus->iORegisters[Bus::IORegister::DMA0CNT_H + 1 + ioRegOffset] << 8);

    uint8_t startTiming = (control & 0x3000) >> 12;
    if(startTiming == 3) {
        if(x == 1 || x == 2) {
            // sound FIFO mode
            if(false /*TODO: if soundControllerRequest*/) {
            } else {
                return 0;
            }
        } else if(x == 3) {
            // video capture mode
            if(!hBlank) {
                DEBUGWARN("error! videoMode dma outside of hblank, this shouldn't be happening\n");
                return 0;
            }
            if((scanline != 2)) {
                // Capture works similar like HBlank DMA, however, the transfer is started when VCOUNT=2,
                // it is then repeated each scanline, and it gets stopped when VCOUNT=162
                scheduleDmaX(x, (control >> 8), false);
                return 0;
            }
        } else {
            assert(false);
        }
    }

    if(startTiming == 1) {
        if(!vBlank) {
            DEBUGWARN("error! vblank dma outside of vblank, this shouldn't be happening\n");
            return 0;
        }
    }
    if(startTiming == 2) {
        if(!hBlank) {
            DEBUGWARN("error! hblank dma outside of hblank, this shouldn't be happening\n");
        }
        if(scanline > PPU::SCREEN_HEIGHT - 1) {
            scheduleDmaX(x, (control >> 8), false);
            return 0;
        }   
    }

    // TODO: Game Pak DRQ  - DMA3 only -  (0=Normal, 1=DRQ <from> Game Pak, DMA3)    

    // TODO: implement this
    // if(startTiming == 3) {
    //     if(x == 3) {
    //         // video capture mode  using this transfer mode, set the repeat bit, 
    //         //and write the number of data units (per scanline) to the word count register. 
    //         // Capture works similar like HBlank DMA, however, the transfer is started when VCOUNT=2, 
    //         //it is then repeated each scanline, and it gets stopped when VCOUNT=162
    //         uint8_t vCount = bus->iORegisters[Bus::IORegister::VCOUNT];
    //         if((vCount != 2 && !inVideoCaptureMode) || vCount > 162) {
    //             inVideoCaptureMode = false;
    //             return 0;
    //         } else {
    //             inVideoCaptureMode = true;
    //         }
    //     } 
    // }

    bus->resetCycleCountTimeline();

    // TODO: TEMPORARY CYCLE COUNTING UNTIL WAITSTATES DONE PROPERLY
    uint32_t tempCycles = 2;

    // TODO: Internal time for DMA processing is 2I (normally), or 4I (if both source and destination are in gamepak memory area).
    //bus->addCycleToExecutionTimeline(Bus::INTERNAL, 0, 0);
    //bus->addCycleToExecutionTimeline(Bus::INTERNAL, 0, 0);

    if(!dmaXEnabled[x]) {
        dmaXEnabled[x] = true;

         // 40000B0h,0B2h - DMA0SAD - DMA 0 Source Address (W) (internal memory)
        dmaXSourceAddr[x] = (uint32_t)(bus->iORegisters[Bus::IORegister::DMA0SAD + ioRegOffset]) |
                            ((uint32_t)(bus->iORegisters[Bus::IORegister::DMA0SAD + 1 + ioRegOffset]) << 8) | 
                            ((uint32_t)(bus->iORegisters[Bus::IORegister::DMA0SAD + 2 + ioRegOffset]) << 16) | 
                            ((uint32_t)(bus->iORegisters[Bus::IORegister::DMA0SAD + 3 + ioRegOffset]) << 24);

        // 40000B4h,0B6h - DMA0DAD - DMA 0 Destination Address (W) (internal memory)
        dmaXDestAddr[x] = (uint32_t)(bus->iORegisters[Bus::IORegister::DMA0DAD + ioRegOffset]) |
                            ((uint32_t)(bus->iORegisters[Bus::IORegister::DMA0DAD + 1 + ioRegOffset]) << 8) | 
                            ((uint32_t)(bus->iORegisters[Bus::IORegister::DMA0DAD + 2 + ioRegOffset]) << 16) | 
                            ((uint32_t)(bus->iORegisters[Bus::IORegister::DMA0DAD + 3 + ioRegOffset]) << 24);

        // 40000B8h - DMA0CNT_L - DMA 0 Word Count (W) (14 bit, 1..4000h)
        dmaXWordCount[x] = (uint32_t)(bus->iORegisters[Bus::IORegister::DMA0CNT_L + ioRegOffset]) |
                            ((uint32_t)(bus->iORegisters[Bus::IORegister::DMA0CNT_L + 1 + ioRegOffset]) << 8);
        

        // SPECIAL BEHAVIOURS FOR DIFFERENT X
        switch(x) {
            case 0: {
                dmaXSourceAddr[x] &= internalMemMask;
                dmaXDestAddr[x] &= internalMemMask;

                if(dmaXWordCount[x] == 0) {
                    dmaXWordCount[x] = dma012MaxWordCount;
                } else {
                    dmaXWordCount[x] &= dma012WordMask;
                }
                break;
            }
            case 1: {
                dmaXSourceAddr[x] &= anyMemMask;
                dmaXDestAddr[x] &= internalMemMask;

                if(dmaXWordCount[x] == 0) {
                    dmaXWordCount[x] = dma012MaxWordCount;
                } else {
                    dmaXWordCount[x] &= dma012WordMask;
                }
                break;
            }
            case 2: {
                dmaXSourceAddr[x] &= anyMemMask;
                dmaXDestAddr[x] &= internalMemMask;

                if(dmaXWordCount[x] == 0) {
                    dmaXWordCount[x] = dma012MaxWordCount;
                } else {
                    dmaXWordCount[x] &= dma012WordMask;
                }
                break;
            }
            case 3: {
                dmaXSourceAddr[x] &= anyMemMask;
                dmaXDestAddr[x] &= anyMemMask;

                if(dmaXWordCount[x] == 0) {
                    dmaXWordCount[x] = dma3MaxWordCount;
                } else {
                    dmaXWordCount[x] &= dma3WordMask;
                }
                break;
            }
            default: {
                assert(false);
                break;
            }
        }
    }


    if(!eepromBusWidthDetected) {
        uint32_t dest = dmaXDestAddr[x];
        if(x == 3) {
            if(dmaXWordCount[x] == 17) {
                // 14 bit width eeprom 
                eepromBusWidthDetected = true;
                bus->setEepromBusWidth(14);
            } else if(dmaXWordCount[x] == 9) {
                // 6 but width eeprom
                eepromBusWidthDetected = true;
                bus->setEepromBusWidth(6);
            }
        }
    }

    bool thirtyTwoBit = control & 0x0400; //  (0=16bit, 1=32bit)
    bool firstAccess = true;
 
    uint8_t destAdjust = (control & 0x0060) >> 5;
    uint8_t srcAdjust = (control & 0x0180) >> 7;
    assert(srcAdjust != 3);

    uint32_t offset = thirtyTwoBit ? 4 : 2;

    // writing / reading from memeory
    // TODO: implement DMA open bus (some games are dependent on it and wil help pass mgba test suite)
    for(uint32_t i = 0; i < dmaXWordCount[x]; i++) {
        if(thirtyTwoBit) {
            if(firstAccess) { 
                uint32_t value = bus->read32(dmaXSourceAddr[x] & 0xFFFFFFFC, Bus::CycleType::NONSEQUENTIAL);
                //uint32_t value = bus->read32(dmaXSourceAddr[x], Bus::NONSEQUENTIAL);
                bus->write32(dmaXDestAddr[x] & 0xFFFFFFFC, value, Bus::NONSEQUENTIAL);
                firstAccess = false;
            } else {
                uint32_t value = bus->read32(dmaXSourceAddr[x] & 0xFFFFFFFC, Bus::CycleType::SEQUENTIAL);
                bus->write32(dmaXDestAddr[x] & 0xFFFFFFFC, value, Bus::SEQUENTIAL);
            }
        } else {
            if(firstAccess) { 
                uint16_t value = bus->read16(dmaXSourceAddr[x] & 0xFFFFFFFE, Bus::CycleType::NONSEQUENTIAL);
                bus->write16(dmaXDestAddr[x] & 0xFFFFFFFE, value, Bus::NONSEQUENTIAL);
                firstAccess = false;
            } else {
                uint16_t value = bus->read16(dmaXSourceAddr[x] & 0xFFFFFFFE, Bus::CycleType::SEQUENTIAL);
                bus->write16(dmaXDestAddr[x] & 0xFFFFFFFE, value, Bus::SEQUENTIAL);
            }
        }

        // TODO: TEMPORARY CYCLE COUNTING UNTIL WAITSTATES DONE PROPERLY
        // iterating source memory pointer
        // (0=Increment,1=Decrement,2=Fixed,3=prohibited)
        switch(srcAdjust) {
            case 0: {
                dmaXSourceAddr[x] += offset;
                break;
            }
            case 1: {
                dmaXSourceAddr[x] -= offset;
                break;
            }
            case 2: {
                break;
            }
            default: {
                break;
            }
        }

        // iterating dest memory pointer
        // (0=Increment,1=Decrement,2=Fixed,3=Increment/Reload)
        switch(destAdjust) {
            case 0:
            case 3: {
                dmaXDestAddr[x] += offset;
                break;
            }
            case 1: {
                dmaXDestAddr[x] -= offset;
                break;
            }
            case 2: {
                break;
            }
            default: {
                break;
            }
        }
        GameBoyAdvanceImpl::cyclesSinceStart += 2;

        if(GameBoyAdvanceImpl::cyclesSinceStart >= scheduler->peekNextEvent()->startCycle) {
            // another event occurred during this dma! exit to handle that event
            // while scheduling this one immediately to resume after the event
            if((scheduler->peekNextEvent()->eventType) < Scheduler::convertDmaValToDmaEvent(x)) {
                scheduleDmaX(x, (control >> 8), true);
                dmaXWordCount[x] -= (i + 1);;
                return tempCycles;
            }
        }
    }
    tempCycles += bus->getMemoryAccessCycles();

    if(!(control & 0x0200)) {
        // DMA Repeat (0=Off, 1=On) (Must be zero if Bit 11 set)
        // if not dma repeat, set enable bit = 0 when done transfer
        bus->iORegisters[Bus::IORegister::DMA0CNT_H + 1 + ioRegOffset] &= 0x7F;
        dmaXEnabled[x] = false;
    } else {
        // else, dma repeat is set, so schedule next dma
        if((startTiming == 2 && scanline >= (PPU::SCREEN_HEIGHT - 1)) || startTiming == 1 || 
           (startTiming == 3 && x == 3 && scanline >= 162))
            /* TODO: || (startTiming == 3 && (x == 1 || x == 2) && soundControllerFifoRequest) || 
                        (startTiming == 3 && x == 3 && scanline >= (PPU::SCREEN_HEIGHT - 1))*/ {
            // hblank repeat ends when in vblank, vlbank repeat only runs once per blank
            dmaXEnabled[x] = false;
        }
        dmaXWordCount[x] = (uint32_t)(bus->iORegisters[Bus::IORegister::DMA0CNT_L + ioRegOffset]) |
                    ((uint32_t)(bus->iORegisters[Bus::IORegister::DMA0CNT_L + 1 + ioRegOffset]) << 8);
        if(destAdjust == 3) {
            dmaXDestAddr[x] = (uint32_t)(bus->iORegisters[Bus::IORegister::DMA0DAD + ioRegOffset]) |
                    ((uint32_t)(bus->iORegisters[Bus::IORegister::DMA0DAD + 1 + ioRegOffset]) << 8) | 
                    ((uint32_t)(bus->iORegisters[Bus::IORegister::DMA0DAD + 2 + ioRegOffset]) << 16) | 
                    ((uint32_t)(bus->iORegisters[Bus::IORegister::DMA0DAD + 3 + ioRegOffset]) << 24);
            // TODO: rules for masking dest addr 
        }

        uint16_t control = (uint16_t)(bus->iORegisters[Bus::IORegister::DMA0CNT_H + ioRegOffset]) |
                    (uint16_t)(bus->iORegisters[Bus::IORegister::DMA0CNT_H + 1 + ioRegOffset] << 8);
        
        scheduleDmaX(x, (control >> 8), false);

    }
    if(control & 0x4000) {
        //irq at end of word count
        switch(x) {
            case 0: {
                cpu->queueInterrupt(ARM7TDMI::Interrupt::DMA0);
                break;
            }
            case 1: {
                cpu->queueInterrupt(ARM7TDMI::Interrupt::DMA1);
                break;
            }
            case 2: {
                cpu->queueInterrupt(ARM7TDMI::Interrupt::DMA2);
                break;
            }
            case 3: {
                cpu->queueInterrupt(ARM7TDMI::Interrupt::DMA3);
                break;
            }
            default: {
                break;
            }
        }
    }


    return tempCycles;
}


void DMA::connectBus(std::shared_ptr<Bus> bus) {
    this->bus = bus;
}

void DMA::connectCpu(std::shared_ptr<ARM7TDMI> cpu) {
    this->cpu = cpu;
}


void DMA::updateDmaUponWrite(uint32_t address, uint32_t value, uint8_t width) {

    while(width != 0) {
        uint8_t byte = value & 0xFF;
        switch(address & 0xFF) {
            case 0xBB: {
                // dma 0
                scheduleDmaX(0, byte, false);
                break;
            }
            case 0xC7: {
                // dma 1
                scheduleDmaX(1, byte, false);
                break;
            }
            case 0xD3: {
                // dma 2
                scheduleDmaX(2, byte, false);
                break;
            }
            case 0xDF: {
                //dma 3
                scheduleDmaX(3, byte, false);
                break;
            }
            default: {
                //assert(false);
                break;
            }
        }
        width -= 8;
        address += 1;
        value = value >> 8;
    }
}

void DMA::scheduleDmaX(uint32_t x, uint8_t upperControlByte, bool immediately) {
    Scheduler::EventType eventType;
    switch(x) {
        case 0: {
            eventType = Scheduler::DMA0;
            break;
        }
        case 1: {
            eventType = Scheduler::DMA1;
            break;
        }
        case 2: {
            eventType = Scheduler::DMA2;
            break;
        }
        case 3: {
            eventType = Scheduler::DMA3;
            break;
        }
    }

    // remove old event
    scheduler->removeEvent(eventType);

    if((upperControlByte & 0x80) || immediately) {
        // enabling dma
        uint32_t ioRegOffset =  0xC * x;

        uint8_t startTiming = (upperControlByte & 0x30) >> 4;

        uint32_t cyclesInFuture = 2;
        if(immediately) {
            cyclesInFuture = 0;
        }

        switch(startTiming) {
            case 0: {
                scheduler->addEvent(eventType, cyclesInFuture, Scheduler::EventCondition::NULL_CONDITION, immediately);
                break;
            }
            case 1: {
                scheduler->addEvent(eventType, 0, Scheduler::EventCondition::VBLANK_START, immediately);
                break;
            }
            case 2: {
                scheduler->addEvent(eventType, 0, Scheduler::EventCondition::HBLANK_START, immediately);
                break;
            }
            case 3: {
                // special
                assert(x != 0);
                if(x == 1 || x == 2) {
                    scheduler->addEvent(eventType, cyclesInFuture, Scheduler::EventCondition::NULL_CONDITION, immediately);
                } else {
                    // x == 3
                    scheduler->addEvent(eventType, cyclesInFuture, Scheduler::EventCondition::DMA3_VIDEO_MODE, immediately);
                }

                break;
            }
        }

    } else {
        // disabling dma
        // don't need to add a new dma event
    }
}


void DMA::connectScheduler(std::shared_ptr<Scheduler> scheduler) {
    this->scheduler = scheduler;
}

<|start_filename|>src/arm7tdmi/ARM7TDMI.cpp<|end_filename|>

#include "ARM7TDMI.h"

#include <bit>
#include <bitset>
#include <iostream>
#include <type_traits>
#include <string.h>

#include "../memory/Bus.h"
#include "../Timer.h"
#include "../Debugger.h"

#include "assert.h"


void ARM7TDMI::initializeWithRom() {
    switchToMode(SYSTEM);
    cpsr.Mode = SYSTEM;
    cpsr.T = 0; // set CPU to ARM state
    setRegister(PC_REGISTER, BOOT_LOCATION); 
    currentPcAccessType = BRANCH;
    setRegister(SP_REGISTER, 0x03007F00); // stack pointer
    r13_svc = 0x03007FE0; // SP_svc=03007FE0h
    r13_irq = 0x03007FA0; // SP_irq=03007FA0h

    bus->resetCycleCountTimeline();
    uint32_t pcAddress = getRegister(PC_REGISTER);
    currInstruction = bus->read32(pcAddress, Bus::CycleType::NONSEQUENTIAL);
    // emulate filling the pipeline
    bus->addCycleToExecutionTimeline(Bus::CycleType::SEQUENTIAL, pcAddress + 4, 32);
    bus->addCycleToExecutionTimeline(Bus::CycleType::SEQUENTIAL, pcAddress + 8, 32);
}

ARM7TDMI::~ARM7TDMI() {}

uint32_t ARM7TDMI::getCurrentInstruction() {
    return currInstruction;
}


uint32_t ARM7TDMI::step() {
    // TODO: give this method a better name
    bus->resetCycleCountTimeline();

    if((bus->iORegisters[Bus::IORegister::IME] & 0x1) && 
       (!cpsr.I) &&
       ((bus->iORegisters[Bus::IORegister::IE] & bus->iORegisters[Bus::IORegister::IF]) || 
       ((bus->iORegisters[Bus::IORegister::IE + 1] & 0x3F) & (bus->iORegisters[Bus::IORegister::IF + 1] & 0x3F)))) {
        //Debugger::stepMode = true;
        // interrupts is enabled
        irq();
    }

    if (!cpsr.T) {  // check state bit, is CPU in ARM state?

        uint8_t cond = (currInstruction & 0xF0000000) >> 28;

        // increment PC
        setRegister(PC_REGISTER, getRegister(PC_REGISTER) + 4);
        if(conditionalHolds(cond)) { 
            currentPcAccessType = 
                armLut[((currInstruction & 0x0FF00000) >> 16) | 
                       ((currInstruction & 0x0F0) >> 4)](currInstruction, this);
        } else {
            currentPcAccessType = SEQUENTIAL;
        }
        
    } else {  // THUMB state
        setRegister(PC_REGISTER, getRegister(PC_REGISTER) + 2);
        currentPcAccessType = thumbLut[(currInstruction >> 6)](currInstruction, this);
    }

    getNextInstruction(currentPcAccessType);

    // TODO: just return one cycle per instr for now
    return 1;
}

inline
void ARM7TDMI::getNextInstruction(FetchPCMemoryAccess currentPcAccessType) {
    currInstrAddress = getRegister(PC_REGISTER);
    if(cpsr.T) {
        currInstruction = bus->read16(currInstrAddress, Bus::CycleType::NONSEQUENTIAL);
    } else {
        currInstruction = bus->read32(currInstrAddress, Bus::CycleType::NONSEQUENTIAL);
    }
    return;
}

inline
void ARM7TDMI::irq() {
    uint32_t returnAddr = getRegister(PC_REGISTER) + 4;
    switchToMode(Mode::IRQ);
    // switch to ARM mode
    *currentSpsr = cpsr;
    cpsr.T = 0;
    cpsr.I = 1; 
    cpsr.Mode = Mode::IRQ;
    setRegister(PC_REGISTER, 0x18);
    setRegister(LINK_REGISTER, returnAddr);
    getNextInstruction(FetchPCMemoryAccess::BRANCH);
}

void ARM7TDMI::queueInterrupt(Interrupt interrupt) {
    bus->iORegisters[Bus::IORegister::IF] |= ((uint16_t)interrupt & 0xFF);
    bus->iORegisters[Bus::IORegister::IF + 1] |= (((uint16_t)interrupt >> 8) & 0xFF);
}

void ARM7TDMI::connectBus(std::shared_ptr<Bus> bus) { 
    this->bus = bus; 
}


inline
bool ARM7TDMI::conditionalHolds(uint8_t cond) {
    switch(cond) {
        case Condition::EQ: {
            return cpsr.Z;
        }
        case Condition::NE: {
            return !cpsr.Z;
        }
        case Condition::CS: {
            return cpsr.C;
        }
        case Condition::CC: {
            return !cpsr.C;
        }
        case Condition::MI: {
            return cpsr.N;
        }
        case Condition::PL: {
            return !cpsr.N;
        }
        case Condition::VS: {
            return cpsr.V;
        }
        case Condition::VC: {
            return !cpsr.V;
        }
        case Condition::HI: {
            return cpsr.C && !cpsr.Z;
        }
        case Condition::LS: {
            return !cpsr.C || cpsr.Z;
        }
        case Condition::GE: {
            return cpsr.N == cpsr.V;
        }
        case Condition::LT: {
            return cpsr.N != cpsr.V;
        }
        case Condition::GT: {
            return !cpsr.Z && (cpsr.N == cpsr.V);
        }
        case Condition::LE: {
            return cpsr.Z || (cpsr.N != cpsr.V);
        }
        case Condition::AL: {
            return true;
        }
        case Condition::NV: {
           return false;       
        }
        default: {
            assert(false);
            return false;
        }
    }
}

ARM7TDMI::ProgramStatusRegister *ARM7TDMI::getCurrentModeSpsr() {
    return currentSpsr;
}

ARM7TDMI::ProgramStatusRegister ARM7TDMI::getCpsr() {
    return cpsr;
}


uint32_t ARM7TDMI::getRegister(uint8_t index) { 
    return *(registers[index]); 
}

uint32_t ARM7TDMI::getUserRegister(uint8_t index) {
    return *(userRegisters[index]); 
}

void ARM7TDMI::setRegister(uint8_t index, uint32_t value) {
    *(registers[index]) = value;
}

void ARM7TDMI::setUserRegister(uint8_t index, uint32_t value) {
    *(userRegisters[index]) = value;
}

inline
uint8_t ARM7TDMI::getOpcode(uint32_t instruction) {
    return (instruction & 0x01E00000) >> 21;
}

inline
bool ARM7TDMI::sFlagSet(uint32_t instruction) {
    return (instruction & 0x00100000);
}


void ARM7TDMI::setCurrInstruction(uint32_t instruction) {
    currInstruction = instruction;
}


/*
  Bit   Expl.
  31    N - Sign Flag       (0=Not Signed, 1=Signed)               ;\
  30    Z - Zero Flag       (0=Not Zero, 1=Zero)                   ; Condition
  29    C - Carry Flag      (0=Borrow/No Carry, 1=Carry/No Borrow) ; Code Flags
  28    V - Overflow Flag   (0=No Overflow, 1=Overflow)            ;/
  27    Q - Sticky Overflow (1=Sticky Overflow, ARMv5TE and up only)
  26-25 Reserved            (For future use) - Do not change manually!
  24    J - Jazelle Mode    (1=Jazelle Bytecode instructions) (if supported)
  23-10 Reserved            (For future use) - Do not change manually!
  9     E - Endian          (... Big endian)                  (ARM11 ?)
  8     A - Abort disable   (1=Disable Imprecise Data Aborts) (ARM11 only)
  7     I - IRQ disable     (0=Enable, 1=Disable)                     ;\
  6     F - FIQ disable     (0=Enable, 1=Disable)                     ; Control
  5     T - State Bit       (0=ARM, 1=THUMB) - Do not change manually!; Bits
  4-0   M4-M0 - Mode Bits   (See below)                               ;/
eturn value;

*/

<|start_filename|>src/arm7tdmi/ARMInstructions/ArmHwdtHandler.h<|end_filename|>

#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"



template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::armHwdtHandler(uint32_t instruction, ARM7TDMI* cpu) {

    constexpr bool l = op & 0x010;
    constexpr bool p = op & 0x100;
    constexpr bool u = op & 0x080;
    constexpr bool w = op & 0x020;
    constexpr bool i = op & 0x040;
    constexpr uint8_t opcode = (op & 0x006) >> 1;

    assert((instruction & 0x0E000000) == 0);

    uint8_t rd = getRd(instruction);
    uint32_t rdVal =
        (rd == 15) ? cpu->getRegister(rd) + 8 : cpu->getRegister(rd);
    uint8_t rn = getRn(instruction);
    uint32_t rnVal =
        (rn == 15) ? cpu->getRegister(rn) + 4 : cpu->getRegister(rn);

    uint32_t offset = 0;

    if constexpr(i) {
        // immediate as offset
        offset = (((instruction & 0x00000F00) >> 4) | (instruction & 0x0000000F));
    } else {
        // register as offset
        assert(!(instruction & 0x00000F00));
        assert(getRm(instruction) != 15);
        offset = cpu->getRegister(getRm(instruction));
    }
    assert(instruction & 0x00000080);
    assert(instruction & 0x00000010);

    uint32_t address = rnVal;
    if constexpr(p) {
        // pre-indexing offset
        if constexpr(u) {
            address = address + offset;
        } else {
            address = address - offset;
        }
        if constexpr(w) {
            // write address back into base register
            cpu->setRegister(rn, address);
        }
    } else {
        // post-indexing offset
        assert(dataTransGetW(instruction) == 0);
        // add offset after transfer and always write back
        if constexpr(u) {
            cpu->setRegister(rn, address + offset);
        } else {
            cpu->setRegister(rn, address - offset);
        }
    }

    if constexpr(opcode  == 0) {
        // Reserved for SWP instruction
        assert(false);
    } else if constexpr(opcode == 1) { // STRH or LDRH (depending on l)
        if constexpr(l) {  // LDR{cond}H  Rd,<Address>  ;Load Unsigned halfword
                    // (zero-extended)         
            cpu->setRegister(rd, aluShiftRor(
                                        (uint32_t)(cpu->bus->read16(address, Bus::CycleType::NONSEQUENTIAL)),
                                        (address & 1) * 8));
            cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
        } else {  // STR{cond}H  Rd,<Address>  ;Store halfword   [a]=Rd
            cpu->bus->write16(address, (uint16_t)rdVal, Bus::CycleType::NONSEQUENTIAL);
        }

    } else if constexpr(opcode == 2) {// LDR{cond}SB Rd,<Address>  ;Load Signed byte (sign extended)
        // TODO: better way to do this?
        assert(l);
        uint32_t val = (uint32_t)(cpu->bus->read8(address, Bus::CycleType::NONSEQUENTIAL));
        if (val & 0x00000080) {
            cpu->setRegister(rd, 0xFFFFFF00 | val);
        } else {
            cpu->setRegister(rd, val);
        }
        cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
    } else {
        // opcode == 3
        // LDR{cond}SH Rd,<Address>  ;Load Signed halfword (sign extended)
        if(address & 0x00000001) {
            // TODO refactor this, reusing the same code as case 2
            // strange case: LDRSH Rd,[odd]  -->  LDRSB Rd,[odd] ;sign-expand BYTE value
            assert(dataTransGetL(instruction));
            uint32_t val = (uint32_t)(cpu->bus->read8(address, Bus::CycleType::NONSEQUENTIAL));
            if (val & 0x00000080) {
                cpu->setRegister(rd, 0xFFFFFF00 | val);
            } else {
                cpu->setRegister(rd, val);
            }
        } else {
            assert(l);
            uint32_t val = (uint32_t)(cpu->bus->read16(address, Bus::CycleType::NONSEQUENTIAL));
            if (val & 0x00008000) {
                cpu->setRegister(rd, 0xFFFF0000 | val);
            } else {
                cpu->setRegister(rd, val);
            }
            cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
        }
    }
    if constexpr(l) {
        if(rd == PC_REGISTER) {
            // LDR PC
            return BRANCH;
        } else {
            return NONSEQUENTIAL;
        }
    } else {
        return NONSEQUENTIAL;
    }

}

<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbLdStImmOffHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

// op = instruction & 0xFFC0;
// anything within the mask can be constexpr!

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbLdStImmOffHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xE000) == 0x6000);
    // uint8_t opcode = (instruction & 0x1800) >> 11;
    constexpr uint8_t opcode = (op & 0x060) >> 5;
    uint8_t rd = thumbGetRd(instruction);
    uint8_t rb = thumbGetRb(instruction);

    if constexpr(opcode == 0) {
        // 0: STR  Rd,[Rb,#nn]  ;store 32bit data   WORD[Rb+nn] = Rd
        uint32_t offset = (instruction & 0x07C0) >> 4;
        uint32_t address = cpu->getRegister(rb) + offset;
        cpu->bus->write32(address, cpu->getRegister(rd), Bus::CycleType::NONSEQUENTIAL);
    } else if constexpr(opcode == 1) {
        // 1: LDR  Rd,[Rb,#nn]  ;load  32bit data   Rd = WORD[Rb+nn]
        uint32_t offset = (instruction & 0x07C0) >> 4;
        uint32_t address = cpu->getRegister(rb) + offset;
        uint32_t value = aluShiftRor(cpu->bus->read32(address, 
                                                      Bus::CycleType::NONSEQUENTIAL),
                                     (address & 3) * 8);
        // if(rd == PC_REGISTER) {
        //     // For THUMB code, the low bit of the target address may/should/must be set,
        //     // the bit is (or is not) interpreted as thumb-bit (depending on the opcode), 
        //     // and R15 is then forcibly aligned by clearing the lower bit.
        //     // TODO: check that this is applied everywhere
        //     cpu->setRegister(rd, value & 0xFFFFFFFE);
        // } else {
        //     cpu->setRegister(rd, value);
        // }
        cpu->setRegister(rd, value);
        cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
    } else if constexpr(opcode == 2) {
        // 2: STRB Rd,[Rb,#nn]  ;store  8bit data   BYTE[Rb+nn] = Rd
        uint32_t offset = (instruction & 0x07C0) >> 6;
        uint32_t address = cpu->getRegister(rb) + offset;
        cpu->bus->write8(address, (uint8_t)(cpu->getRegister(rd)), Bus::CycleType::NONSEQUENTIAL);
    } else {
        // 3: LDRB Rd,[Rb,#nn]  ;load   8bit data   Rd = BYTE[Rb+nn]
        uint32_t offset = (instruction & 0x07C0) >> 6;
        uint32_t address = cpu->getRegister(rb) + offset;
        cpu->setRegister(rd, (uint32_t)cpu->bus->read8(address, Bus::CycleType::NONSEQUENTIAL));
        cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
    }

    return NONSEQUENTIAL;
}


<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbLdStSeBHwHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

// op = instruction & 0xFFC0;
// anything within the mask can be constexpr!

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess
ARM7TDMI::thumbLdStSeBHwHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xF000) == 0x5000);
    assert(instruction & 0x0200);
    //uint8_t opcode = (instruction & 0x0C00) >> 10;
    constexpr uint8_t opcode = (op & 0x030) >> 4;
    uint8_t rd = thumbGetRd(instruction);
    uint8_t rb = thumbGetRb(instruction);
    //uint8_t ro = (instruction & 0x01C0) >> 6;
    constexpr uint8_t ro = (op & 0x007);
    uint32_t address = cpu->getRegister(rb) + cpu->getRegister(ro);

    Cycles cycles;

    if constexpr(opcode == 0) {
        // 0: STRH Rd,[Rb,Ro]  ;store 16bit data          HALFWORD[Rb+Ro] =
        // Rd
        cpu->bus->write16(address, cpu->getRegister(rd), Bus::CycleType::NONSEQUENTIAL);
    } else if constexpr(opcode == 1) {
        // 1: LDSB Rd,[Rb,Ro]  ;load sign-extended 8bit   Rd = BYTE[Rb+Ro]
        uint32_t value = cpu->bus->read8(address, Bus::CycleType::NONSEQUENTIAL);
        value = (value & 0x80) ? (0xFFFFFF00 | value) : value;
        cpu->setRegister(rd, value);
        cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);         
    } else if constexpr(opcode == 2) {
        // 2: LDRH Rd,[Rb,Ro]  ;load zero-extended 16bit  Rd =
        // HALFWORD[Rb+Ro]
        uint32_t value = aluShiftRor(cpu->bus->read16(address, Bus::CycleType::NONSEQUENTIAL),
                                        (address & 1) * 8);
        cpu->setRegister(rd, value);
        cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0);
    } else {
        // 3: LDSH Rd,[Rb,Ro]  ;load sign-extended 16bit  Rd = HALFWORD[Rb+Ro]
        if(address & 0x1) {
            // LDRSH Rd,[odd]  -->  LDRSB Rd,[odd]         ;sign-expand BYTE value
            // if address odd
            uint32_t value = cpu->bus->read8(address, Bus::CycleType::NONSEQUENTIAL);
            value = (value & 0x80) ? (0xFFFFFF00 | value) : value;
            cpu->setRegister(rd, value);
        } else {
            uint32_t value = aluShiftRor(cpu->bus->read16(address, Bus::CycleType::NONSEQUENTIAL),
                                            (address & 1) * 8);
            value = (value & 0x8000) ? (0xFFFF0000 | value) : value;
            cpu->setRegister(rd, value);
        }
        cpu->bus->addCycleToExecutionTimeline(Bus::CycleType::INTERNAL, 0, 0); 
    }

    return NONSEQUENTIAL;
}


<|start_filename|>test/testCommon.h<|end_filename|>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <iterator>
#include <vector>
#include <bitset>
#include <assert.h>


#define ASSERT_EQUAL(message, expected, actual) \
    if (expected != actual) { \
        DEBUG("ASSERTION FAILED: " << message <<  \
        " expected: " << expected << " != actual: " << actual << \
        "\n"); assert(false); } 

typedef struct cpu_log {
    uint32_t address;
    uint32_t instruction;
    uint32_t cpsr;
    uint32_t r[16];
    uint32_t cycles;
} cpu_log_t;

std::vector<cpu_log> getLogs(std::string path) {
    std::ifstream logFile;
    logFile.open(path);
    std::string line;

    std::vector<cpu_log> logs;
    std::string delimiter = ",";
    while (std::getline(logFile, line)) {
        // std::cout << line << std::endl;
        int currentPos = 0;
        int nextPos = (line.find(delimiter, currentPos) == std::string::npos)
                          ? line.size()
                          : line.find(delimiter, currentPos);
        std::string value;
        cpu_log log;

        value = line.substr(currentPos, nextPos - currentPos);
        log.address = std::stoul(value, nullptr, 16);
        currentPos = nextPos + 1;
        nextPos = (line.find(delimiter, currentPos) == std::string::npos)
                      ? line.size()
                      : line.find(delimiter, currentPos);

        value = line.substr(currentPos, nextPos - currentPos);
        log.instruction = std::stoul(value, nullptr, 16);
        currentPos = nextPos + 1;
        nextPos = (line.find(delimiter, currentPos) == std::string::npos)
                      ? line.size()
                      : line.find(delimiter, currentPos);

        value = line.substr(currentPos, nextPos - currentPos);
        log.cpsr = std::stoul(value, nullptr, 16);
        currentPos = nextPos + 1;
        nextPos = (line.find(delimiter, currentPos) == std::string::npos)
                      ? line.size()
                      : line.find(delimiter, currentPos);

        for (int i = 0; i < 16; i++) {
            value = line.substr(currentPos, nextPos - currentPos);
            log.r[i] = std::stoul(value, nullptr, 16);
            currentPos = nextPos + 1;
            nextPos = (line.find(delimiter, currentPos) == std::string::npos)
                          ? line.size()
                          : line.find(delimiter, currentPos);
        }

        value = line.substr(currentPos, nextPos - currentPos);
        log.cycles = std::stoul(value, nullptr, 10);
        currentPos = nextPos + 1;
        nextPos = (line.find(delimiter, currentPos) == std::string::npos)
                      ? line.size()
                      : line.find(delimiter, currentPos);

        logs.push_back(log);
    }
    return logs;
};



<|start_filename|>test/testThumb.cpp<|end_filename|>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <iterator>
#include <vector>
#include <bitset>
#include <assert.h>

#include "../src/arm7tdmi/ARM7TDMI.h"
#include "../src/memory/Bus.h"
#include "../src/GameBoyAdvanceImpl.h"
#include "../src/PPU.h"
#include "testCommon.h"

int main() {
    std::vector<cpu_log> logs = getLogs("thumb.log");

    GameBoyAdvanceImpl gba;
    gba.loadRom("thumb.gba");
    int count = 0;
    ARM7TDMI* cpu = gba.getCpu();

    for(cpu_log log : logs) {
        // DEBUG("expected r15:\t" << log.r[15] << std::endl);
        // DEBUG("actual r15:\t" << cpu->getRegister(15) + 2 << std::endl);

        uint32_t instrAddress = cpu->getRegister(15);
        std::cout << "expectedAddr:\t" << log.address << std::endl;
        std::cout << "actualAddr:\t" << instrAddress << std::endl;
        ASSERT_EQUAL("address", (uint32_t)log.address, instrAddress)

        uint32_t cpsr = cpu->psrToInt(cpu->getCpsr());
        std::cout << "expectedCpsr:\t" << log.cpsr << std::endl;
        std::cout << "actualCpsr:\t" << cpsr << std::endl;
        std::cout << "expectedCpsr bits\t" << std::bitset<32>(log.cpsr).to_string() << std::endl;
        std::cout << "actualCpsr bits\t\t" << std::bitset<32>(cpsr).to_string() << std::endl;

        ASSERT_EQUAL("cpsr", (uint32_t)log.cpsr, (uint32_t)cpsr)

        ASSERT_EQUAL("r0", log.r[0], cpu->getRegister(0))
        ASSERT_EQUAL("r1", log.r[1], cpu->getRegister(1))
        ASSERT_EQUAL("r2", log.r[2], cpu->getRegister(2))
        ASSERT_EQUAL("r3", log.r[3], cpu->getRegister(3))
        ASSERT_EQUAL("r4", log.r[4], cpu->getRegister(4))
        ASSERT_EQUAL("r5", log.r[5], cpu->getRegister(5))
        ASSERT_EQUAL("r6", log.r[6], cpu->getRegister(6))
        ASSERT_EQUAL("r7", log.r[7], cpu->getRegister(7))
        ASSERT_EQUAL("r8", log.r[8], cpu->getRegister(8))
        ASSERT_EQUAL("r9", log.r[9], cpu->getRegister(9))
        ASSERT_EQUAL("r10", log.r[10], cpu->getRegister(10))
        ASSERT_EQUAL("r11", log.r[11], cpu->getRegister(11))
        ASSERT_EQUAL("r12", log.r[12], cpu->getRegister(12))
        ASSERT_EQUAL("r13", log.r[13], cpu->getRegister(13))
        ASSERT_EQUAL("r14", log.r[14], cpu->getRegister(14))
        if(log.cpsr & 0x00000020) {
            // in thumb state add 2 to PC to account for pipelining
            ASSERT_EQUAL("r15", log.r[15], cpu->getRegister(15) + 2)
        } else {
            // in arm state add 4 to PC to account for pipelining
            ASSERT_EQUAL("r15", log.r[15], cpu->getRegister(15) + 4)
        }
        //bus.printCurrentExecutionTimeline();
        
        std::cout << std::endl;
        std::cout << "instruction " << count << std::endl;
        count++;

        std::cout << "expectedInstr:\t" << log.instruction << std::endl;
        std::cout << "actualInstr:\t" << cpu->getCurrentInstruction() << std::endl;
        std::cout << "actualInstr bits\t" << std::bitset<16>(cpu->getCurrentInstruction()).to_string() << std::endl;
        ASSERT_EQUAL("instruction", (uint32_t)log.instruction, (uint32_t)cpu->getCurrentInstruction());

        cpu->step();

    }

    return 0;
}


<|start_filename|>src/memory/EEPROM.h<|end_filename|>
#pragma once

#include <cstdint>
#include <array>


class EEPROM {

    public:
        void transferBitToEeprom(bool bit);
        uint32_t receiveBitFromEeprom();
        static constexpr uint32_t SIX_BIT_WRITE_SIZE = 73;
        static constexpr uint32_t FOURTEEN_BIT_WRITE_SIZE = 81;
        static constexpr uint32_t SIX_BIT_READ_SIZE = 9;
        static constexpr uint32_t FOURTEEN_BIT_READ_SIZE = 17;
        enum {
            READ_OP = 3,
            WRITE_OP = 1
        };

        void setBusWidth(uint32_t width);

    private:
        
        uint32_t busWidth = 0;
        uint32_t writeSize = 0;
        uint32_t readSize = 0;
        long currTransferBit = 0;
        long currReceivingBit = 0;
        uint32_t address;
        int currAddressBit;
        uint32_t currTransferSize;
        int currWriteValueBit;
        uint32_t currReadValueBit = 63;
        uint64_t valueToRead;
        uint64_t valueToWrite;
        bool readyToRead;
        bool writeComplete;
        bool firstBit;
        uint8_t op; // 2 = write, 3 = read TODO: ENUM

        std::array<uint64_t, 0x400> eeprom;
};

<|start_filename|>src/memory/Flash.h<|end_filename|>
#pragma once
#include <cstdint>
#include <array>

class Flash {

    public: 
        enum Mode {
            READY,
            CHIP_ID,
            ERASE,
            WRITE,
            MEM_BANK
        };

        void write(uint32_t address, uint8_t value);
        uint8_t read(uint32_t address);

        void setSize(uint32_t size);


    private:
        Mode currMode = READY;
        int currStage = 0;
        std::array<uint8_t, 0x20000> flash;
        // different ids for 64k and 128k
        uint8_t manufacturerId = 0x62;
        uint8_t deviceId = 0x13;
        uint8_t temp0x0 = 0xFF;
        uint8_t temp0x1 = 0xFF;
        uint32_t bank = 0x0;

        bool is4KbEraseCommand(uint32_t address, uint8_t value);

};


<|start_filename|>src/arm7tdmi/ThumbInstructions/ThumbShiftHandler.h<|end_filename|>
#pragma once
#include "../ARM7TDMI.h"
#include "../../memory/Bus.h"

template<uint16_t op>
ARM7TDMI::FetchPCMemoryAccess ARM7TDMI::thumbShiftHandler(uint16_t instruction, ARM7TDMI* cpu) {
    assert((instruction & 0xE000) == 0);

    //uint8_t opcode = (instruction & 0x1800) >> 11;
    constexpr uint8_t opcode = (op & 0x060) >> 5;
    uint8_t offset = (instruction & 0x07C0) >> 6;
    //constexpr uint8_t offset = (op & 0x01F);
    uint8_t rs = thumbGetRs(instruction);
    uint8_t rd = thumbGetRd(instruction);

    uint32_t rsVal = cpu->getRegister(rs);

    bool carryFlag;
    uint32_t result;
    if constexpr(opcode == 0) {
        // 00b: LSL{S} Rd,Rs,#Offset (logical/arithmetic shift left)
        if(offset == 0) {
            result = rsVal;
            carryFlag = cpu->cpsr.C;
        } else {
            result = aluShiftLsl(rsVal, offset);
            // TODO: put the logic for shift carry bit into functions
            carryFlag = aluLslSetsCarryBit(rsVal, offset);

        }
    } else if constexpr(opcode == 1) {
        // 01b: LSR{S} Rd,Rs,#Offset (logical shift right)
        if(offset == 0) {
            result = aluShiftLsr(rsVal, 32);
            carryFlag = aluLsrSetsCarryBit(rsVal, 32);
        } else {
            result = aluShiftLsr(rsVal, offset);
            carryFlag = aluLsrSetsCarryBit(rsVal, offset);
        }
        
    } else if constexpr(opcode == 2) {
        // 10b: ASR{S} Rd,Rs,#Offset (arithmetic shift right)
        if(offset == 0) {
            result = aluShiftAsr(rsVal, 32);
            carryFlag = aluAsrSetsCarryBit(rsVal, 32);
        } else {
            result = aluShiftAsr(rsVal, offset);
            carryFlag = aluAsrSetsCarryBit(rsVal, offset);
        }
    } else {
        // opcode == 3
        // 11b: Reserved (used for add/subtract instructions)
        assert(false);
    }

    cpu->setRegister(rd, result);
    cpu->cpsr.C = carryFlag;
    cpu->cpsr.Z = (result == 0);
    cpu->cpsr.N = (bool)(result & 0x80000000);
   
    return SEQUENTIAL;
}