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 }