1 module gbaid.gba.system; 2 3 import gbaid.util; 4 5 import gbaid.gba.display; 6 import gbaid.gba.cpu; 7 import gbaid.gba.memory; 8 import gbaid.gba.dma; 9 import gbaid.gba.interrupt; 10 import gbaid.gba.halt; 11 import gbaid.gba.keypad; 12 import gbaid.gba.sound; 13 import gbaid.gba.timer; 14 import gbaid.gba.sio; 15 16 public class GameBoyAdvance { 17 private static enum size_t CYCLE_BATCH_SIZE = CYCLES_PER_DOT * 4; 18 private MemoryBus memory; 19 private ARM7TDMI processor; 20 private InterruptHandler interruptHandler; 21 private HaltHandler haltHandler; 22 private Display display; 23 private Keypad keypad; 24 private SoundChip soundChip; 25 private Timers timers; 26 private DMAs dmas; 27 private SerialPort serialPort; 28 private int lastBiosValidRead; 29 private size_t displayCycles = 0; 30 private size_t processorCycles = 0; 31 private size_t dmasCycles = 0; 32 private size_t timersCycles = 0; 33 private size_t soundChipCycles = 0; 34 private size_t keypadCycles = 0; 35 private size_t serialPortCycles = 0; 36 37 public this(void[] bios, GamePakData gamePakData, uint serialIndex = 0) { 38 memory = MemoryBus(bios, gamePakData); 39 memory.biosReadFallback = &biosReadFallback; 40 memory.unusedMemory = &unusedReadFallBack; 41 42 auto ioRegisters = memory.ioRegisters; 43 44 processor = new ARM7TDMI(&memory, BIOS_START); 45 haltHandler = new HaltHandler(ioRegisters, processor); 46 interruptHandler = new InterruptHandler(ioRegisters, processor, haltHandler); 47 keypad = new Keypad(ioRegisters, interruptHandler); 48 dmas = new DMAs(&memory, ioRegisters, interruptHandler, haltHandler); 49 soundChip = new SoundChip(ioRegisters, dmas); 50 timers = new Timers(ioRegisters, interruptHandler, soundChip); 51 serialPort = new SerialPort(ioRegisters, interruptHandler, serialIndex); 52 display = new Display(ioRegisters, memory.palette, memory.vram, memory.oam, interruptHandler, dmas); 53 54 memory.biosReadGuard = &biosReadGuard; 55 memory.gamePak.interruptHandler = interruptHandler; 56 } 57 58 @property public FrameSwapper frameSwapper() { 59 return display.frameSwapper; 60 } 61 62 @property public void audioReceiver(AudioReceiver receiver) { 63 soundChip.receiver = receiver; 64 } 65 66 @property public GamePakData gamePakSaveData() { 67 return memory.gamePak.saveData; 68 } 69 70 @property public void serialCommunication(Communication communication) { 71 serialPort.communication = communication; 72 } 73 74 public void setKeypadState(KeypadState state) { 75 keypad.setState(state); 76 } 77 78 public void emulate(size_t cycles) { 79 // If an exception occurs during emulation, swap the frame to release any thread waiting on this one 80 scope (failure) { 81 frameSwapper.swapFrame(); 82 } 83 // Split cycles into batches, and process these first 84 auto fullBatches = cycles / CYCLE_BATCH_SIZE; 85 foreach (i; 0 .. fullBatches) { 86 displayCycles = display.emulate(displayCycles + CYCLE_BATCH_SIZE); 87 processorCycles = processor.emulate(processorCycles + CYCLE_BATCH_SIZE); 88 dmasCycles = dmas.emulate(dmasCycles + CYCLE_BATCH_SIZE); 89 timersCycles = timers.emulate(timersCycles + CYCLE_BATCH_SIZE); 90 soundChipCycles = soundChip.emulate(soundChipCycles + CYCLE_BATCH_SIZE); 91 keypadCycles = keypad.emulate(keypadCycles + CYCLE_BATCH_SIZE); 92 serialPortCycles = serialPort.emulate(serialPortCycles + CYCLE_BATCH_SIZE); 93 } 94 // An incomplete batch of cycles might be left over, so process it too 95 auto partialBatch = cycles % CYCLE_BATCH_SIZE; 96 if (partialBatch > 0) { 97 displayCycles = display.emulate(displayCycles + partialBatch); 98 processorCycles = processor.emulate(processorCycles + partialBatch); 99 dmasCycles = dmas.emulate(dmasCycles + partialBatch); 100 timersCycles = timers.emulate(timersCycles + partialBatch); 101 soundChipCycles = soundChip.emulate(soundChipCycles + partialBatch); 102 keypadCycles = keypad.emulate(keypadCycles + partialBatch); 103 serialPortCycles = serialPort.emulate(serialPortCycles + partialBatch); 104 } 105 } 106 107 private bool biosReadGuard(uint address) { 108 if (cast(uint) processor.getProgramCounter() < BIOS_SIZE) { 109 if (address < BIOS_SIZE) { 110 lastBiosValidRead = memory.bios.get!int(address & IntAlignMask!int); 111 } 112 return true; 113 } 114 return false; 115 } 116 117 private int biosReadFallback(uint address) { 118 return rotateRead(address, lastBiosValidRead); 119 } 120 121 private int unusedReadFallBack(uint address) { 122 return rotateRead(address, processor.getPreFetch()); 123 } 124 }