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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +1 -0
- data/.rubocop.yml +54 -0
- data/Gemfile +32 -0
- data/Gemfile.lock +267 -0
- data/LICENSE +21 -0
- data/README.md +115 -0
- data/Steepfile +7 -0
- data/exe/amaterasu +23 -0
- data/lib/amaterasu/cartridge/mbc1.rb +56 -0
- data/lib/amaterasu/cartridge/rom.rb +118 -0
- data/lib/amaterasu/cartridge.rb +68 -0
- data/lib/amaterasu/cli.rb +60 -0
- data/lib/amaterasu/emulator.rb +121 -0
- data/lib/amaterasu/game_boy/apu.rb +12 -0
- data/lib/amaterasu/game_boy/bus.rb +161 -0
- data/lib/amaterasu/game_boy/cpu/instructions/adc.rb +64 -0
- data/lib/amaterasu/game_boy/cpu/instructions/add16.rb +73 -0
- data/lib/amaterasu/game_boy/cpu/instructions/add8.rb +63 -0
- data/lib/amaterasu/game_boy/cpu/instructions/and.rb +62 -0
- data/lib/amaterasu/game_boy/cpu/instructions/base.rb +38 -0
- data/lib/amaterasu/game_boy/cpu/instructions/call.rb +48 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_bit.rb +52 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_res.rb +49 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_rl.rb +70 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_rlc.rb +68 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_rr.rb +70 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_rrc.rb +68 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_set.rb +51 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_sla.rb +69 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_sra.rb +71 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_srl.rb +69 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_swap.rb +67 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cp.rb +61 -0
- data/lib/amaterasu/game_boy/cpu/instructions/daa.rb +59 -0
- data/lib/amaterasu/game_boy/cpu/instructions/dec.rb +64 -0
- data/lib/amaterasu/game_boy/cpu/instructions/di.rb +21 -0
- data/lib/amaterasu/game_boy/cpu/instructions/ei.rb +19 -0
- data/lib/amaterasu/game_boy/cpu/instructions/halt.rb +19 -0
- data/lib/amaterasu/game_boy/cpu/instructions/inc.rb +64 -0
- data/lib/amaterasu/game_boy/cpu/instructions/jp.rb +54 -0
- data/lib/amaterasu/game_boy/cpu/instructions/jr.rb +45 -0
- data/lib/amaterasu/game_boy/cpu/instructions/ld16.rb +79 -0
- data/lib/amaterasu/game_boy/cpu/instructions/ld8.rb +210 -0
- data/lib/amaterasu/game_boy/cpu/instructions/ldh.rb +61 -0
- data/lib/amaterasu/game_boy/cpu/instructions/misc.rb +53 -0
- data/lib/amaterasu/game_boy/cpu/instructions/nop.rb +19 -0
- data/lib/amaterasu/game_boy/cpu/instructions/or.rb +56 -0
- data/lib/amaterasu/game_boy/cpu/instructions/pop.rb +39 -0
- data/lib/amaterasu/game_boy/cpu/instructions/push.rb +43 -0
- data/lib/amaterasu/game_boy/cpu/instructions/ret.rb +70 -0
- data/lib/amaterasu/game_boy/cpu/instructions/rotate.rb +120 -0
- data/lib/amaterasu/game_boy/cpu/instructions/rst.rb +33 -0
- data/lib/amaterasu/game_boy/cpu/instructions/sbc.rb +64 -0
- data/lib/amaterasu/game_boy/cpu/instructions/stop.rb +19 -0
- data/lib/amaterasu/game_boy/cpu/instructions/sub.rb +63 -0
- data/lib/amaterasu/game_boy/cpu/instructions/xor.rb +60 -0
- data/lib/amaterasu/game_boy/cpu/instructions.rb +600 -0
- data/lib/amaterasu/game_boy/cpu/registers.rb +264 -0
- data/lib/amaterasu/game_boy/cpu.rb +232 -0
- data/lib/amaterasu/game_boy/dma.rb +114 -0
- data/lib/amaterasu/game_boy/interrupts.rb +108 -0
- data/lib/amaterasu/game_boy/joypad.rb +127 -0
- data/lib/amaterasu/game_boy/oam/sprite.rb +106 -0
- data/lib/amaterasu/game_boy/oam.rb +29 -0
- data/lib/amaterasu/game_boy/ppu/modes/disabled.rb +29 -0
- data/lib/amaterasu/game_boy/ppu/modes/h_blank.rb +45 -0
- data/lib/amaterasu/game_boy/ppu/modes/oam_scan.rb +93 -0
- data/lib/amaterasu/game_boy/ppu/modes/rendering/bg_win_fetcher.rb +204 -0
- data/lib/amaterasu/game_boy/ppu/modes/rendering/pixel_emitter.rb +83 -0
- data/lib/amaterasu/game_boy/ppu/modes/rendering/pixel_fifo.rb +70 -0
- data/lib/amaterasu/game_boy/ppu/modes/rendering/sprite_fetcher.rb +140 -0
- data/lib/amaterasu/game_boy/ppu/modes/rendering.rb +108 -0
- data/lib/amaterasu/game_boy/ppu/modes/v_blank.rb +43 -0
- data/lib/amaterasu/game_boy/ppu/modes.rb +22 -0
- data/lib/amaterasu/game_boy/ppu/registers/lcd_control.rb +57 -0
- data/lib/amaterasu/game_boy/ppu/registers/lcd_status.rb +88 -0
- data/lib/amaterasu/game_boy/ppu/registers.rb +131 -0
- data/lib/amaterasu/game_boy/ppu.rb +207 -0
- data/lib/amaterasu/game_boy/ram.rb +70 -0
- data/lib/amaterasu/game_boy/serial.rb +91 -0
- data/lib/amaterasu/game_boy/timer.rb +230 -0
- data/lib/amaterasu/game_boy/vram/tile.rb +68 -0
- data/lib/amaterasu/game_boy/vram/tile_data.rb +52 -0
- data/lib/amaterasu/game_boy/vram/tile_map.rb +71 -0
- data/lib/amaterasu/game_boy/vram.rb +51 -0
- data/lib/amaterasu/hal/console.rb +23 -0
- data/lib/amaterasu/hal/sdl2/bindings.rb +59 -0
- data/lib/amaterasu/hal/sdl2.rb +127 -0
- data/lib/amaterasu/utils/bit_ops.rb +22 -0
- data/lib/amaterasu.rb +13 -0
- data/sig/akane/cartridge/rom.rbs +29 -0
- data/sig/akane/cartridge.rbs +12 -0
- data/sig/akane/cli.rbs +16 -0
- data/sig/akane/emulator.rbs +19 -0
- data/sig/akane/game_boy/apu.rbs +7 -0
- data/sig/akane/game_boy/bus.rbs +25 -0
- data/sig/akane/game_boy/cpu/instructions/adc.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/add16.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions/add8.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/and.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/base.rbs +20 -0
- data/sig/akane/game_boy/cpu/instructions/call.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/cb_bit.rbs +20 -0
- data/sig/akane/game_boy/cpu/instructions/cb_res.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions/cb_rl.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_rlc.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_rr.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_rrc.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_set.rbs +20 -0
- data/sig/akane/game_boy/cpu/instructions/cb_sla.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_sra.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_srl.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_swap.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cp.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/daa.rbs +17 -0
- data/sig/akane/game_boy/cpu/instructions/dec.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions/di.rbs +13 -0
- data/sig/akane/game_boy/cpu/instructions/ei.rbs +13 -0
- data/sig/akane/game_boy/cpu/instructions/halt.rbs +13 -0
- data/sig/akane/game_boy/cpu/instructions/inc.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions/jp.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/jr.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/ld16.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/ld8.rbs +31 -0
- data/sig/akane/game_boy/cpu/instructions/ldh.rbs +23 -0
- data/sig/akane/game_boy/cpu/instructions/misc.rbs +20 -0
- data/sig/akane/game_boy/cpu/instructions/nop.rbs +13 -0
- data/sig/akane/game_boy/cpu/instructions/or.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/pop.rbs +17 -0
- data/sig/akane/game_boy/cpu/instructions/push.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/ret.rbs +20 -0
- data/sig/akane/game_boy/cpu/instructions/rotate.rbs +23 -0
- data/sig/akane/game_boy/cpu/instructions/rst.rbs +17 -0
- data/sig/akane/game_boy/cpu/instructions/sbc.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions/stop.rbs +13 -0
- data/sig/akane/game_boy/cpu/instructions/sub.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/xor.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions.rbs +12 -0
- data/sig/akane/game_boy/cpu/registers.rbs +56 -0
- data/sig/akane/game_boy/cpu.rbs +39 -0
- data/sig/akane/game_boy/interrupts.rbs +28 -0
- data/sig/akane/game_boy/joypad.rbs +25 -0
- data/sig/akane/game_boy/oam/sprite.rbs +30 -0
- data/sig/akane/game_boy/ppu/modes/disabled.rbs +17 -0
- data/sig/akane/game_boy/ppu/modes/h_blank.rbs +20 -0
- data/sig/akane/game_boy/ppu/modes/oam_scan.rbs +28 -0
- data/sig/akane/game_boy/ppu/modes/rendering.rbs +26 -0
- data/sig/akane/game_boy/ppu/modes/v_blank.rbs +20 -0
- data/sig/akane/game_boy/ppu/modes.rbs +13 -0
- data/sig/akane/game_boy/ppu.rbs +59 -0
- data/sig/akane/game_boy/ram.rbs +16 -0
- data/sig/akane/game_boy/serial.rbs +21 -0
- data/sig/akane/game_boy/timer.rbs +30 -0
- data/sig/akane/utils/bit_ops.rbs +11 -0
- data/sig/akane.rbs +3 -0
- 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
|