amaterasu 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +54 -0
  5. data/Gemfile +32 -0
  6. data/Gemfile.lock +267 -0
  7. data/LICENSE +21 -0
  8. data/README.md +115 -0
  9. data/Steepfile +7 -0
  10. data/exe/amaterasu +23 -0
  11. data/lib/amaterasu/cartridge/mbc1.rb +56 -0
  12. data/lib/amaterasu/cartridge/rom.rb +118 -0
  13. data/lib/amaterasu/cartridge.rb +68 -0
  14. data/lib/amaterasu/cli.rb +60 -0
  15. data/lib/amaterasu/emulator.rb +121 -0
  16. data/lib/amaterasu/game_boy/apu.rb +12 -0
  17. data/lib/amaterasu/game_boy/bus.rb +161 -0
  18. data/lib/amaterasu/game_boy/cpu/instructions/adc.rb +64 -0
  19. data/lib/amaterasu/game_boy/cpu/instructions/add16.rb +73 -0
  20. data/lib/amaterasu/game_boy/cpu/instructions/add8.rb +63 -0
  21. data/lib/amaterasu/game_boy/cpu/instructions/and.rb +62 -0
  22. data/lib/amaterasu/game_boy/cpu/instructions/base.rb +38 -0
  23. data/lib/amaterasu/game_boy/cpu/instructions/call.rb +48 -0
  24. data/lib/amaterasu/game_boy/cpu/instructions/cb_bit.rb +52 -0
  25. data/lib/amaterasu/game_boy/cpu/instructions/cb_res.rb +49 -0
  26. data/lib/amaterasu/game_boy/cpu/instructions/cb_rl.rb +70 -0
  27. data/lib/amaterasu/game_boy/cpu/instructions/cb_rlc.rb +68 -0
  28. data/lib/amaterasu/game_boy/cpu/instructions/cb_rr.rb +70 -0
  29. data/lib/amaterasu/game_boy/cpu/instructions/cb_rrc.rb +68 -0
  30. data/lib/amaterasu/game_boy/cpu/instructions/cb_set.rb +51 -0
  31. data/lib/amaterasu/game_boy/cpu/instructions/cb_sla.rb +69 -0
  32. data/lib/amaterasu/game_boy/cpu/instructions/cb_sra.rb +71 -0
  33. data/lib/amaterasu/game_boy/cpu/instructions/cb_srl.rb +69 -0
  34. data/lib/amaterasu/game_boy/cpu/instructions/cb_swap.rb +67 -0
  35. data/lib/amaterasu/game_boy/cpu/instructions/cp.rb +61 -0
  36. data/lib/amaterasu/game_boy/cpu/instructions/daa.rb +59 -0
  37. data/lib/amaterasu/game_boy/cpu/instructions/dec.rb +64 -0
  38. data/lib/amaterasu/game_boy/cpu/instructions/di.rb +21 -0
  39. data/lib/amaterasu/game_boy/cpu/instructions/ei.rb +19 -0
  40. data/lib/amaterasu/game_boy/cpu/instructions/halt.rb +19 -0
  41. data/lib/amaterasu/game_boy/cpu/instructions/inc.rb +64 -0
  42. data/lib/amaterasu/game_boy/cpu/instructions/jp.rb +54 -0
  43. data/lib/amaterasu/game_boy/cpu/instructions/jr.rb +45 -0
  44. data/lib/amaterasu/game_boy/cpu/instructions/ld16.rb +79 -0
  45. data/lib/amaterasu/game_boy/cpu/instructions/ld8.rb +210 -0
  46. data/lib/amaterasu/game_boy/cpu/instructions/ldh.rb +61 -0
  47. data/lib/amaterasu/game_boy/cpu/instructions/misc.rb +53 -0
  48. data/lib/amaterasu/game_boy/cpu/instructions/nop.rb +19 -0
  49. data/lib/amaterasu/game_boy/cpu/instructions/or.rb +56 -0
  50. data/lib/amaterasu/game_boy/cpu/instructions/pop.rb +39 -0
  51. data/lib/amaterasu/game_boy/cpu/instructions/push.rb +43 -0
  52. data/lib/amaterasu/game_boy/cpu/instructions/ret.rb +70 -0
  53. data/lib/amaterasu/game_boy/cpu/instructions/rotate.rb +120 -0
  54. data/lib/amaterasu/game_boy/cpu/instructions/rst.rb +33 -0
  55. data/lib/amaterasu/game_boy/cpu/instructions/sbc.rb +64 -0
  56. data/lib/amaterasu/game_boy/cpu/instructions/stop.rb +19 -0
  57. data/lib/amaterasu/game_boy/cpu/instructions/sub.rb +63 -0
  58. data/lib/amaterasu/game_boy/cpu/instructions/xor.rb +60 -0
  59. data/lib/amaterasu/game_boy/cpu/instructions.rb +600 -0
  60. data/lib/amaterasu/game_boy/cpu/registers.rb +264 -0
  61. data/lib/amaterasu/game_boy/cpu.rb +232 -0
  62. data/lib/amaterasu/game_boy/dma.rb +114 -0
  63. data/lib/amaterasu/game_boy/interrupts.rb +108 -0
  64. data/lib/amaterasu/game_boy/joypad.rb +127 -0
  65. data/lib/amaterasu/game_boy/oam/sprite.rb +106 -0
  66. data/lib/amaterasu/game_boy/oam.rb +29 -0
  67. data/lib/amaterasu/game_boy/ppu/modes/disabled.rb +29 -0
  68. data/lib/amaterasu/game_boy/ppu/modes/h_blank.rb +45 -0
  69. data/lib/amaterasu/game_boy/ppu/modes/oam_scan.rb +93 -0
  70. data/lib/amaterasu/game_boy/ppu/modes/rendering/bg_win_fetcher.rb +204 -0
  71. data/lib/amaterasu/game_boy/ppu/modes/rendering/pixel_emitter.rb +83 -0
  72. data/lib/amaterasu/game_boy/ppu/modes/rendering/pixel_fifo.rb +70 -0
  73. data/lib/amaterasu/game_boy/ppu/modes/rendering/sprite_fetcher.rb +140 -0
  74. data/lib/amaterasu/game_boy/ppu/modes/rendering.rb +108 -0
  75. data/lib/amaterasu/game_boy/ppu/modes/v_blank.rb +43 -0
  76. data/lib/amaterasu/game_boy/ppu/modes.rb +22 -0
  77. data/lib/amaterasu/game_boy/ppu/registers/lcd_control.rb +57 -0
  78. data/lib/amaterasu/game_boy/ppu/registers/lcd_status.rb +88 -0
  79. data/lib/amaterasu/game_boy/ppu/registers.rb +131 -0
  80. data/lib/amaterasu/game_boy/ppu.rb +207 -0
  81. data/lib/amaterasu/game_boy/ram.rb +70 -0
  82. data/lib/amaterasu/game_boy/serial.rb +91 -0
  83. data/lib/amaterasu/game_boy/timer.rb +230 -0
  84. data/lib/amaterasu/game_boy/vram/tile.rb +68 -0
  85. data/lib/amaterasu/game_boy/vram/tile_data.rb +52 -0
  86. data/lib/amaterasu/game_boy/vram/tile_map.rb +71 -0
  87. data/lib/amaterasu/game_boy/vram.rb +51 -0
  88. data/lib/amaterasu/hal/console.rb +23 -0
  89. data/lib/amaterasu/hal/sdl2/bindings.rb +59 -0
  90. data/lib/amaterasu/hal/sdl2.rb +127 -0
  91. data/lib/amaterasu/utils/bit_ops.rb +22 -0
  92. data/lib/amaterasu.rb +13 -0
  93. data/sig/akane/cartridge/rom.rbs +29 -0
  94. data/sig/akane/cartridge.rbs +12 -0
  95. data/sig/akane/cli.rbs +16 -0
  96. data/sig/akane/emulator.rbs +19 -0
  97. data/sig/akane/game_boy/apu.rbs +7 -0
  98. data/sig/akane/game_boy/bus.rbs +25 -0
  99. data/sig/akane/game_boy/cpu/instructions/adc.rbs +18 -0
  100. data/sig/akane/game_boy/cpu/instructions/add16.rbs +19 -0
  101. data/sig/akane/game_boy/cpu/instructions/add8.rbs +18 -0
  102. data/sig/akane/game_boy/cpu/instructions/and.rbs +18 -0
  103. data/sig/akane/game_boy/cpu/instructions/base.rbs +20 -0
  104. data/sig/akane/game_boy/cpu/instructions/call.rbs +18 -0
  105. data/sig/akane/game_boy/cpu/instructions/cb_bit.rbs +20 -0
  106. data/sig/akane/game_boy/cpu/instructions/cb_res.rbs +19 -0
  107. data/sig/akane/game_boy/cpu/instructions/cb_rl.rbs +21 -0
  108. data/sig/akane/game_boy/cpu/instructions/cb_rlc.rbs +21 -0
  109. data/sig/akane/game_boy/cpu/instructions/cb_rr.rbs +21 -0
  110. data/sig/akane/game_boy/cpu/instructions/cb_rrc.rbs +21 -0
  111. data/sig/akane/game_boy/cpu/instructions/cb_set.rbs +20 -0
  112. data/sig/akane/game_boy/cpu/instructions/cb_sla.rbs +21 -0
  113. data/sig/akane/game_boy/cpu/instructions/cb_sra.rbs +21 -0
  114. data/sig/akane/game_boy/cpu/instructions/cb_srl.rbs +21 -0
  115. data/sig/akane/game_boy/cpu/instructions/cb_swap.rbs +21 -0
  116. data/sig/akane/game_boy/cpu/instructions/cp.rbs +18 -0
  117. data/sig/akane/game_boy/cpu/instructions/daa.rbs +17 -0
  118. data/sig/akane/game_boy/cpu/instructions/dec.rbs +19 -0
  119. data/sig/akane/game_boy/cpu/instructions/di.rbs +13 -0
  120. data/sig/akane/game_boy/cpu/instructions/ei.rbs +13 -0
  121. data/sig/akane/game_boy/cpu/instructions/halt.rbs +13 -0
  122. data/sig/akane/game_boy/cpu/instructions/inc.rbs +19 -0
  123. data/sig/akane/game_boy/cpu/instructions/jp.rbs +18 -0
  124. data/sig/akane/game_boy/cpu/instructions/jr.rbs +18 -0
  125. data/sig/akane/game_boy/cpu/instructions/ld16.rbs +18 -0
  126. data/sig/akane/game_boy/cpu/instructions/ld8.rbs +31 -0
  127. data/sig/akane/game_boy/cpu/instructions/ldh.rbs +23 -0
  128. data/sig/akane/game_boy/cpu/instructions/misc.rbs +20 -0
  129. data/sig/akane/game_boy/cpu/instructions/nop.rbs +13 -0
  130. data/sig/akane/game_boy/cpu/instructions/or.rbs +18 -0
  131. data/sig/akane/game_boy/cpu/instructions/pop.rbs +17 -0
  132. data/sig/akane/game_boy/cpu/instructions/push.rbs +18 -0
  133. data/sig/akane/game_boy/cpu/instructions/ret.rbs +20 -0
  134. data/sig/akane/game_boy/cpu/instructions/rotate.rbs +23 -0
  135. data/sig/akane/game_boy/cpu/instructions/rst.rbs +17 -0
  136. data/sig/akane/game_boy/cpu/instructions/sbc.rbs +19 -0
  137. data/sig/akane/game_boy/cpu/instructions/stop.rbs +13 -0
  138. data/sig/akane/game_boy/cpu/instructions/sub.rbs +18 -0
  139. data/sig/akane/game_boy/cpu/instructions/xor.rbs +19 -0
  140. data/sig/akane/game_boy/cpu/instructions.rbs +12 -0
  141. data/sig/akane/game_boy/cpu/registers.rbs +56 -0
  142. data/sig/akane/game_boy/cpu.rbs +39 -0
  143. data/sig/akane/game_boy/interrupts.rbs +28 -0
  144. data/sig/akane/game_boy/joypad.rbs +25 -0
  145. data/sig/akane/game_boy/oam/sprite.rbs +30 -0
  146. data/sig/akane/game_boy/ppu/modes/disabled.rbs +17 -0
  147. data/sig/akane/game_boy/ppu/modes/h_blank.rbs +20 -0
  148. data/sig/akane/game_boy/ppu/modes/oam_scan.rbs +28 -0
  149. data/sig/akane/game_boy/ppu/modes/rendering.rbs +26 -0
  150. data/sig/akane/game_boy/ppu/modes/v_blank.rbs +20 -0
  151. data/sig/akane/game_boy/ppu/modes.rbs +13 -0
  152. data/sig/akane/game_boy/ppu.rbs +59 -0
  153. data/sig/akane/game_boy/ram.rbs +16 -0
  154. data/sig/akane/game_boy/serial.rbs +21 -0
  155. data/sig/akane/game_boy/timer.rbs +30 -0
  156. data/sig/akane/utils/bit_ops.rbs +11 -0
  157. data/sig/akane.rbs +3 -0
  158. metadata +226 -0
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Amaterasu
4
+ module GameBoy
5
+ class Cpu
6
+ # Models the behavior of the SM83 CPU registers.
7
+ #
8
+ # - Contains 8 8-bit core registers.
9
+ # - 8-bit registers can be combined into 16-bit register pair.
10
+ class Registers
11
+ include Utils::BitOps
12
+
13
+ # Returns the 8-bit value stored in the A (Accumulator) register.
14
+ attr_reader :a
15
+
16
+ # Returns the 8-bit value stored in the F (Flags) register.
17
+ attr_reader :f
18
+
19
+ # Returns the 8-bit value stored in the B register.
20
+ attr_reader :b
21
+
22
+ # Returns the 8-bit value stored in the C register.
23
+ attr_reader :c
24
+
25
+ # Returns the 8-bit value stored in the D register.
26
+ attr_reader :d
27
+
28
+ # Returns the 8-bit value stored in the E register.
29
+ attr_reader :e
30
+
31
+ # Returns the 8-bit value stored in the H register.
32
+ attr_reader :h
33
+
34
+ # Returns the 8-bit value stored in the L register.
35
+ attr_reader :l
36
+
37
+ # Returns the 16-bit value stored in the SP (Stack Pointer) register.
38
+ attr_reader :sp
39
+
40
+ # Returns the 16-bit value stored in the PC (Program Counter) register.
41
+ attr_reader :pc
42
+
43
+ # Sets the initial state of the CPU registers.
44
+ #
45
+ # - The values can change based on the Boot ROM implementation.
46
+ # - If the Boot ROM is not skipped, all values should start as 0.
47
+ def initialize(skip_boot_rom: true)
48
+ @a = skip_boot_rom ? 0x01 : 0x00
49
+ @f = skip_boot_rom ? 0b10110000 : 0b00000000
50
+ @b = 0x00
51
+ @c = skip_boot_rom ? 0x13 : 0x00
52
+ @d = 0x00
53
+ @e = skip_boot_rom ? 0xD8 : 0x00
54
+ @h = skip_boot_rom ? 0x01 : 0x00
55
+ @l = skip_boot_rom ? 0x4D : 0x00
56
+
57
+ @sp = skip_boot_rom ? 0xFFFE : 0x0000
58
+ @pc = skip_boot_rom ? 0x0100 : 0x0000
59
+ end
60
+
61
+ # Stores a 8-bit value into the A (Accumulator) register.
62
+ #
63
+ # - CPU ignores any value larger than 255 (0xFF).
64
+ def a=(value)
65
+ @a = value & 0xFF
66
+ end
67
+
68
+ # Stores a 8-bit value into the F (Flags) register.
69
+ #
70
+ # - CPU ignores any value larger than 255 (0xFF).
71
+ # - The lower nibble is always ignored by the CPU.
72
+ def f=(value)
73
+ @f = value & 0b11110000
74
+ end
75
+
76
+ # Stores a 8-bit value into the B register.
77
+ #
78
+ # - CPU ignores any value larger than 255 (0xFF).
79
+ def b=(value)
80
+ @b = value & 0xFF
81
+ end
82
+
83
+ # Stores a 8-bit value into the C register.
84
+ #
85
+ # - CPU ignores any value larger than 255 (0xFF).
86
+ def c=(value)
87
+ @c = value & 0xFF
88
+ end
89
+
90
+ # Stores a 8-bit value into the D register.
91
+ #
92
+ # - CPU ignores any value larger than 255 (0xFF).
93
+ def d=(value)
94
+ @d = value & 0xFF
95
+ end
96
+
97
+ # Stores a 8-bit value into the E register.
98
+ #
99
+ # - CPU ignores any value larger than 255 (0xFF).
100
+ def e=(value)
101
+ @e = value & 0xFF
102
+ end
103
+
104
+ # Stores a 8-bit value into the H register.
105
+ #
106
+ # - CPU ignores any value larger than 255 (0xFF).
107
+ def h=(value)
108
+ @h = value & 0xFF
109
+ end
110
+
111
+ # Stores a 8-bit value into the L register.
112
+ #
113
+ # - CPU ignores any value larger than 255 (0xFF).
114
+ def l=(value)
115
+ @l = value & 0xFF
116
+ end
117
+
118
+ # Sets the value of the AF combined 16-bit register.
119
+ #
120
+ # - Higher byte is set into the A register.
121
+ # - Lower byte is set into the F register.
122
+ # - CPU ignores bits 0-3 of the F register.
123
+ def af=(value)
124
+ value &= 0xFFFF
125
+ @a = (value >> 8) & 0xFF
126
+ @f = value & 0xF0
127
+ end
128
+
129
+ # Sets the value of the BC combined 16-bit register.
130
+ #
131
+ # - Higher byte is set into the B register.
132
+ # - Lower byte is set into the C register.
133
+ def bc=(value)
134
+ value &= 0xFFFF
135
+ @b = (value >> 8) & 0xFF
136
+ @c = value & 0xFF
137
+ end
138
+
139
+ # Sets the value of the DE combined 16-bit register.
140
+ #
141
+ # - Higher byte is set into the D register.
142
+ # - Lower byte is set into the E register.
143
+ def de=(value)
144
+ value &= 0xFFFF
145
+ @d = (value >> 8) & 0xFF
146
+ @e = value & 0xFF
147
+ end
148
+
149
+ # Sets the value of the HL combined 16-bit register.
150
+ #
151
+ # - Higher byte is set into the H register.
152
+ # - Lower byte is set into the L register.
153
+ def hl=(value)
154
+ value &= 0xFFFF
155
+ @h = (value >> 8) & 0xFF
156
+ @l = value & 0xFF
157
+ end
158
+
159
+ # Sets the value of the SP (Stack Pointer) register.
160
+ #
161
+ # - Points to the memory address in which the Stack is currently located.
162
+ # - If something is pushed into the Stack, the SP is decreased
163
+ # - If something is popped from the Stack, the SP is increased
164
+ def sp=(value)
165
+ @sp = value & 0xFFFF
166
+ end
167
+
168
+ # Sets the value of the PC (Program Counter) register.
169
+ #
170
+ # This register points to where in memory is the current instruction.
171
+ # Every time a instruction is fetched and executed,
172
+ # the PC is incremented to fetch the next one.
173
+ def pc=(value)
174
+ @pc = value & 0xFFFF
175
+ end
176
+
177
+ # Returns a 16-bit value stored in the combined AF register.
178
+ #
179
+ # - Shifts the byte stored in the A register 8 bits to the left to get the higher byte.
180
+ # - Performs a bitwise OR with the F register value to add the lower byte.
181
+ def af
182
+ (@a << 8) | @f
183
+ end
184
+
185
+ # Returns a 16-bit value stored in the combined BC register.
186
+ #
187
+ # - Shifts the byte stored in the B register 8 bits to the left to get the higher byte.
188
+ # - Performs a bitwise OR with the C register value to add the lower byte.
189
+ def bc
190
+ (@b << 8) | @c
191
+ end
192
+
193
+ # Returns a 16-bit value stored in the combined DE register.
194
+ #
195
+ # - Shifts the byte stored in the D register 8 bits to the left to get the higher byte.
196
+ # - Performs a bitwise OR with the E register value to add the lower byte.
197
+ def de
198
+ (@d << 8) | @e
199
+ end
200
+
201
+ # Returns a 16-bit value stored in the combined HL register.
202
+ #
203
+ # - Shifts the byte stored in the H register 8 bits to the left to get the higher byte.
204
+ # - Performs a bitwise OR with the L register value to add the lower byte.
205
+ def hl
206
+ (@h << 8) | @l
207
+ end
208
+
209
+ # Returns the current value of the Zero bit flag.
210
+ #
211
+ # - The Zero flag is Bit 7 of the Flags register.
212
+ def z_flag
213
+ bit(@f, 7)
214
+ end
215
+
216
+ # Returns the current value of the Subtraction bit flag.
217
+ #
218
+ # - The Subtraction flag is Bit 6 of the Flags register.
219
+ def n_flag
220
+ bit(@f, 6)
221
+ end
222
+
223
+ # Returns the current value of the Half Carry bit flag.
224
+ #
225
+ # - The Half Carry flag is Bit 5 of the Flags register.
226
+ def h_flag
227
+ bit(@f, 5)
228
+ end
229
+
230
+ # Returns the current value of the Carry bit flag.
231
+ #
232
+ # - The Carry flag is Bit 4 of the Flags register.
233
+ def c_flag
234
+ bit(@f, 4)
235
+ end
236
+
237
+ # Either sets or clears the value of the Zero flag (Bit 7).
238
+ def z_flag=(set)
239
+ @f = set ? set_bit(@f, 7) : clear_bit(@f, 7)
240
+ end
241
+
242
+ # Either sets or clears the value of the Subtraction flag (Bit 6).
243
+ def n_flag=(set)
244
+ @f = set ? set_bit(@f, 6) : clear_bit(@f, 6)
245
+ end
246
+
247
+ # Either sets or clears the value of the Half Carry flag (Bit 5).
248
+ def h_flag=(set)
249
+ @f = set ? set_bit(@f, 5) : clear_bit(@f, 5)
250
+ end
251
+
252
+ # Either sets or clears the value of the Carry flag (Bit 4).
253
+ def c_flag=(set)
254
+ @f = set ? set_bit(@f, 4) : clear_bit(@f, 4)
255
+ end
256
+
257
+ # Clears all 4 flags.
258
+ def clear_flags
259
+ @f = 0x00
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Amaterasu
4
+ module GameBoy
5
+ # Models the CPU behavior from the Game Boy.
6
+ class Cpu
7
+ attr_reader :registers, :m_cycles
8
+
9
+ def initialize(bus, hram, interrupts, advance_cycle, trace_cpu: false)
10
+ @bus = bus
11
+ @hram = hram
12
+ @interrupts = interrupts
13
+ @advance_cycle = advance_cycle
14
+ @trace_cpu = trace_cpu
15
+
16
+ @registers = Registers.new
17
+ @ime = false
18
+ @ime_scheduled = false
19
+ @halted = false
20
+ @opcode = nil
21
+ @instruction = nil
22
+
23
+ @m_cycles = 0
24
+
25
+ @instructions = Instructions.load_base_instructions(cpu: self)
26
+ @cb_instructions = Instructions.load_cb_instructions(cpu: self)
27
+ end
28
+
29
+ # Core CPU loop:
30
+ #
31
+ # - Checks IME and any interrupts pending to be serviced.
32
+ # - Fetches the current Opcode at the Program Counter.
33
+ # - Decodes which instruction based on the Opcode fetched.
34
+ # - Executes the instruction.
35
+ def step
36
+ if @interrupts.any_pending?
37
+ @halted = false if @halted
38
+
39
+ if @ime
40
+ handle_interrupts
41
+ return
42
+ end
43
+ end
44
+
45
+ if @ime_scheduled
46
+ @ime = true
47
+ @ime_scheduled = false
48
+ end
49
+
50
+ if @halted
51
+ @m_cycles = @advance_cycle.call
52
+ return
53
+ end
54
+
55
+ old_pc = @registers.pc
56
+ old_cycles = @m_cycles
57
+ @opcode = fetch_next_byte
58
+ decode_instruction
59
+ execute_instruction
60
+ log_state(old_pc, old_cycles, @instruction)
61
+ end
62
+
63
+ # Reads a byte from the Bus at a given address.
64
+ def bus_read(address:)
65
+ byte = @bus.read_byte(address:, caller: self)
66
+ @m_cycles = @advance_cycle.call
67
+
68
+ byte
69
+ end
70
+
71
+ # Requests a Bus write at a given address with a given value.
72
+ def bus_write(address:, value:)
73
+ @bus.write_byte(address:, value:, caller: self)
74
+ @m_cycles = @advance_cycle.call
75
+ end
76
+
77
+ # Fetches the next immediate byte from memory pointed to by the Program Counter.
78
+ # Every time a byte is fetched, the PC is incremented by 1.
79
+ def fetch_next_byte
80
+ byte = bus_read(address: @registers.pc)
81
+ @registers.pc += 1
82
+
83
+ byte
84
+ end
85
+
86
+ # Fetches the next 2 immediate bytes from memory.
87
+ #
88
+ # - The Game Boy uses little endian format.
89
+ # - This means that the first byte fetched is the least significant one.
90
+ # - So if the memory has these next 2 bytes: $50 $01, the word is: $0150
91
+ def fetch_next_word
92
+ lsb = fetch_next_byte
93
+ msb = fetch_next_byte
94
+
95
+ (msb << 8) | lsb
96
+ end
97
+
98
+ # Pushes a 16-bit value into the Stack.
99
+ #
100
+ # @param value [Integer] 16-bit value to be stored in the Stack.
101
+ def stack_push(value:)
102
+ @registers.sp -= 1
103
+ bus_write(address: @registers.sp, value: (value >> 8) & 0xFF)
104
+ @registers.sp -= 1
105
+ bus_write(address: @registers.sp, value: value & 0xFF)
106
+ end
107
+
108
+ # Pops a 16-bit value from the Stack.
109
+ def stack_pop
110
+ lsb = bus_read(address: @registers.sp)
111
+ @registers.sp += 1
112
+ msb = bus_read(address: @registers.sp)
113
+ @registers.sp += 1
114
+
115
+ (msb << 8) | lsb
116
+ end
117
+
118
+ # Jumps execution to a given address by setting the address value into the PC.
119
+ #
120
+ # @param address [Integer] 16-bit memory address value.
121
+ def jump_to(address:)
122
+ @registers.pc = address
123
+ internal_processing
124
+ end
125
+
126
+ # Converts an unsigned byte into a value between -128 to 127 to use as an offset.
127
+ #
128
+ # @param byte [Integer] 8-bit unsigned value.
129
+ def sign_value(byte)
130
+ byte >= 128 ? (byte - 256) : byte
131
+ end
132
+
133
+ # Performs an addition envolving a 16-bit value,
134
+ # CPU consumes an additional cycle to handle 16-bit values.
135
+ def add16(value1, value2)
136
+ result = value1 + value2
137
+ internal_processing
138
+
139
+ result
140
+ end
141
+
142
+ # Performs a subtraction envolving a 16-bit value,
143
+ # CPU consumes an additional cycle to handle 16-bit values.
144
+ def sub16(value1, value2)
145
+ result = value1 - value2
146
+ internal_processing
147
+
148
+ result
149
+ end
150
+
151
+ # Used by the DI instruction.
152
+ def disable_interrupts
153
+ @ime = false
154
+ end
155
+
156
+ # Used by the EI instruction.
157
+ def enable_interrupts
158
+ @ime_scheduled = true
159
+ end
160
+
161
+ def halt
162
+ @halted = true
163
+ end
164
+
165
+ # Emulates CPU internal processing which advance cycles without Bus access.
166
+ def internal_processing
167
+ @m_cycles = @advance_cycle.call
168
+ end
169
+
170
+ private
171
+
172
+ # Is only called if IME and any interrupt is pending.
173
+ # Takes 5 cycles to complete.
174
+ def handle_interrupts
175
+ @m_cycles = @advance_cycle.call
176
+ @m_cycles = @advance_cycle.call
177
+ @ime = false
178
+ stack_push(value: @registers.pc)
179
+ address_vector = @interrupts.priority_vector
180
+ @interrupts.priority_service
181
+ jump_to(address: address_vector)
182
+ end
183
+
184
+ # Determines which instruction should be executed for each Opcode.
185
+ def decode_instruction
186
+ if @opcode == 0xCB
187
+ @opcode = fetch_next_byte
188
+ @instruction = @cb_instructions[@opcode]
189
+ else
190
+ @instruction = @instructions[@opcode]
191
+ end
192
+ end
193
+
194
+ # Executes the logic for the current instruction.
195
+ def execute_instruction
196
+ @instruction.execute
197
+ end
198
+
199
+ def log_state(old_pc, old_cycles, instruction)
200
+ return unless @trace_cpu
201
+
202
+ $stdout.printf(
203
+ '%<cycles>04d | PC: $%<pc>04X | %<im>-14s (took %<ic>d) | ' \
204
+ '$%<b1>02X $%<b2>02X $%<b3>02X | F: %<f>04b | ' \
205
+ "A: $%<a>02X BC: $%<bc>04X DE: $%<de>04X HL: $%<hl>04X | [HL]: $%<mem_hl>02X\n",
206
+ cycles: @m_cycles,
207
+ pc: old_pc,
208
+ im: instruction.mnemonic,
209
+ ic: @m_cycles - old_cycles,
210
+ b1: @bus.read_byte(address: old_pc),
211
+ b2: @bus.read_byte(address: old_pc + 1),
212
+ b3: @bus.read_byte(address: old_pc + 2),
213
+ f: @registers.f >> 4,
214
+ a: @registers.a,
215
+ bc: @registers.bc,
216
+ de: @registers.de,
217
+ hl: @registers.hl,
218
+ mem_hl: @bus.read_byte(address: @registers.hl)
219
+ )
220
+ end
221
+
222
+ # Custom inspect method to facilitate debugging, prevents circular references
223
+ # since all instructions hold the Cpu object and the Cpu holds instruction objects.
224
+ def inspect
225
+ '#<Amaterasu::GameBoy::Cpu ' \
226
+ "@pc=$#{@registers.pc} " \
227
+ "@instructions_size=#{@instructions.compact.size} " \
228
+ "@cb_instructions_size=#{@cb_instructions.compact.size}>"
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Amaterasu
4
+ module GameBoy
5
+ # Models the RAM chip within the Game Boy.
6
+ #
7
+ # On the real Game Boy, the CPU and DMA share the same physical bus.
8
+ # During DMA, the DMA controller physically takes over the bus lines — the CPU is
9
+ # electrically disconnected from the bus (except HRAM, which is on a separate internal path).
10
+ class Dma
11
+ OAM_START_ADDRESS = 0xFE00
12
+ DMA_START_DELAY = 1
13
+ DMA_TOTAL_CYCLES = 160 + DMA_START_DELAY
14
+
15
+ attr_reader :internal_latch
16
+
17
+ def initialize(bus, trace_dma: false)
18
+ @bus = bus
19
+ @trace_dma = trace_dma
20
+
21
+ @internal_latch = 0xFF
22
+ @source_address = nil
23
+ @target_address = OAM_START_ADDRESS
24
+ @status = :inactive
25
+ @active = false
26
+ @cycles = 0
27
+ end
28
+
29
+ # This method is called once per M-cycle, CPU drives this.
30
+ #
31
+ # - DMA transfer 1 byte per M-cycle, totalling 160 bytes.
32
+ # - It fills the OAM memory range from 0xFE00 - 0xFE9F.
33
+ def tick
34
+ case @status
35
+ when :inactive then nil
36
+ when :pending
37
+ log_state if @trace_dma
38
+ @cycles += 1
39
+ start_transfer
40
+ when :transferring
41
+ @active = true
42
+ source_byte = bus_read(address: @source_address)
43
+ bus_write(address: @target_address, value: source_byte)
44
+
45
+ log_state if @trace_dma
46
+
47
+ @source_address += 1
48
+ @target_address += 1
49
+ @cycles += 1
50
+
51
+ complete_transfer if @cycles == DMA_TOTAL_CYCLES
52
+ when :completed
53
+ @cycles = 0
54
+ @source_address = nil
55
+ @target_address = OAM_START_ADDRESS
56
+ @status = :inactive
57
+ @active = false
58
+ end
59
+ end
60
+
61
+ # When a value is written to 0xFF46, it means a DMA transfer was requested.
62
+ # The value represents the upper byte of the source address.
63
+ # This value is saved (latched), if there is a read from 0xFF46, it should return this value.
64
+ # Requesting a transfer with value 0x80, means the source address is 0x8000.
65
+ # You can multiple the given value by 0x100 to get the source address.
66
+ #
67
+ # @param source_value [Integer] A 8-bit value that represents the upper byte of the source address.
68
+ def request_transfer(source_value:)
69
+ @internal_latch = source_value
70
+ @source_address = source_value * 0x100
71
+ @status = :pending
72
+ @cycles = 0
73
+ end
74
+
75
+ # @return [Boolean] Checks if the DMA transfer is in progress.
76
+ def active?
77
+ @active
78
+ end
79
+
80
+ private
81
+
82
+ def start_transfer
83
+ @status = :transferring
84
+ end
85
+
86
+ def complete_transfer
87
+ @status = :completed
88
+ end
89
+
90
+ def bus_read(address:)
91
+ @bus.read_byte(address:)
92
+ end
93
+
94
+ def bus_write(address:, value:)
95
+ @bus.write_byte(address:, value:)
96
+ end
97
+
98
+ def log_state
99
+ if @cycles.zero?
100
+ puts 'DMA: #000 || START DELAY'
101
+ else
102
+ $stdout.printf(
103
+ "DMA: #%<n>03d || $%<sa>04X ($%<sb>02X) -> $%<ta>04X ($%<tb>02X)\n",
104
+ n: @cycles,
105
+ sa: @source_address,
106
+ sb: @bus.read_byte(address: @source_address),
107
+ ta: @target_address,
108
+ tb: @bus.read_byte(address: @target_address)
109
+ )
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Amaterasu
4
+ module GameBoy
5
+ # Models the Interrupts Controller inside the Game Boy
6
+ class Interrupts
7
+ # Interrupt types sorted by highest to lowest priority.
8
+ TYPES = {
9
+ v_blank: 0,
10
+ lcd_stat: 1,
11
+ timer: 2,
12
+ serial: 3,
13
+ joypad: 4
14
+ }.freeze
15
+
16
+ # Address vectors to jump to for each interrupt type.
17
+ VECTORS = {
18
+ v_blank: 0x0040,
19
+ lcd_stat: 0x0048,
20
+ timer: 0x0050,
21
+ serial: 0x0058,
22
+ joypad: 0x0060
23
+ }.freeze
24
+
25
+ # Creates a new interrupts object holding the registers state.
26
+ #
27
+ # - IF register value changes based on the Boot ROM being skipped.
28
+ def initialize(skip_boot_rom: true)
29
+ @if = skip_boot_rom ? 0xE1 : 0x00
30
+ @ie = 0x00
31
+ end
32
+
33
+ # Returns the 8-bit value of the IF (Interrupt Flag) register.
34
+ #
35
+ # - When read, it always returns 1 for Bits 7, 6 and 5.
36
+ def if_register
37
+ @if | 0b11100000
38
+ end
39
+
40
+ # TODO: convert to attr_reader
41
+ # Returns the 8-bit value of the IE (Interrupt Enable) register.
42
+ def ie_register
43
+ @ie
44
+ end
45
+
46
+ # Sets a 8-bit value into the IF register.
47
+ #
48
+ # - Bits 7, 6 and 5 are set to 0 when writing to it.
49
+ # - Components are responsible for requesting their own interrupts.
50
+ # - Controls whether or not a given interrupt is currently requested.
51
+ def if_register=(value)
52
+ @if = value & 0b00011111
53
+ end
54
+
55
+ # Sets a 8-bit value into the IE register.
56
+ #
57
+ # - Values are set by the game's code during CPU fetch-decode.
58
+ # - Controls whether or not a given interrupt is enabled and can be serviced.
59
+ def ie_register=(value)
60
+ @ie = value & 0xFF
61
+ end
62
+
63
+ # Checks if any interrupt is requested and enabled at the same time.
64
+ #
65
+ # - Ignores the 3 upper bits that are not bound to any interrupt type.
66
+ def any_pending?
67
+ (@if & @ie).anybits?(0b00011111)
68
+ end
69
+
70
+ # Returns which interrupt is currently requested and enabled, sorted by priority.
71
+ def highest_pending
72
+ TYPES.each_key do |type|
73
+ return type if pending?(type)
74
+ end
75
+
76
+ raise ArgumentError, 'No interrupts found for highest pending'
77
+ end
78
+
79
+ # Sets the correct Bit in the IF register based on the interrupt type.
80
+ def request(type)
81
+ set_mask = 1 << TYPES[type]
82
+ @if |= set_mask
83
+ end
84
+
85
+ # Clears the correct Bit in the IF register based on the interrupt type.
86
+ def service(type)
87
+ clear_mask = ~(1 << TYPES[type])
88
+ @if &= clear_mask
89
+ end
90
+
91
+ def priority_service
92
+ service(highest_pending)
93
+ end
94
+
95
+ def priority_vector
96
+ VECTORS[highest_pending]
97
+ end
98
+
99
+ private
100
+
101
+ # Checks if a given interrupt is requested and enabled at the same time.
102
+ def pending?(type)
103
+ set_mask = (1 << TYPES[type])
104
+ ((@if & set_mask) & (@ie & set_mask)).anybits?(0b00011111)
105
+ end
106
+ end
107
+ end
108
+ end