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,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Cpu
|
|
6
|
+
module Instructions
|
|
7
|
+
# Holds the logic of all the 16-bit Load instructions.
|
|
8
|
+
class Ld16 < Base
|
|
9
|
+
def initialize(cpu:, target:, source:)
|
|
10
|
+
super(cpu:)
|
|
11
|
+
|
|
12
|
+
@mnemonic = "LD #{format_operand(target)}, #{format_operand(source)}"
|
|
13
|
+
@logic = build_logic(target, source)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def build_logic(target, source)
|
|
19
|
+
return -> { ld_mem_imm16_sp } if target == :mem_imm16
|
|
20
|
+
|
|
21
|
+
case target
|
|
22
|
+
when :bc then -> { @registers.bc = @cpu.fetch_next_word }
|
|
23
|
+
when :de then -> { @registers.de = @cpu.fetch_next_word }
|
|
24
|
+
when :hl
|
|
25
|
+
case source
|
|
26
|
+
when :imm16 then -> { @registers.hl = @cpu.fetch_next_word }
|
|
27
|
+
when :sp_plus_sig8
|
|
28
|
+
lambda do
|
|
29
|
+
unsigned_byte = @cpu.fetch_next_byte
|
|
30
|
+
signed_value = @cpu.sign_value(unsigned_byte)
|
|
31
|
+
sp = @registers.sp
|
|
32
|
+
result = @cpu.add16(@registers.sp, signed_value)
|
|
33
|
+
|
|
34
|
+
@registers.clear_flags
|
|
35
|
+
@registers.h_flag = (sp & 0x0F) + (unsigned_byte & 0x0F) > 0x0F
|
|
36
|
+
@registers.c_flag = (sp & 0xFF) + (unsigned_byte & 0xFF) > 0xFF
|
|
37
|
+
|
|
38
|
+
@registers.hl = result
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
raise ArgumentError, 'Unknown Ld16 source for LD HL'
|
|
42
|
+
end
|
|
43
|
+
when :sp
|
|
44
|
+
case source
|
|
45
|
+
when :imm16 then -> { @registers.sp = @cpu.fetch_next_word }
|
|
46
|
+
when :hl
|
|
47
|
+
lambda do
|
|
48
|
+
@cpu.internal_processing
|
|
49
|
+
@registers.sp = @registers.hl
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
raise ArgumentError, 'Unknown Ld16 source for LD SP'
|
|
53
|
+
end
|
|
54
|
+
else
|
|
55
|
+
raise ArgumentError, 'Unknown Ld16 target'
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Loads the LSB from SP to the imm16 address.
|
|
60
|
+
# Loads the MSB from SP to the imm16 address + 1.
|
|
61
|
+
#
|
|
62
|
+
# M-cycle 1: Fetches the opcode.
|
|
63
|
+
# M-cycle 2: Fetches the LSB of the address to be used.
|
|
64
|
+
# M-cycle 3: Fetches the MSB of the address to be used.
|
|
65
|
+
# M-cycle 4: Writes the LSB of SP into the address.
|
|
66
|
+
# M-cycle 5: Writes the MSB of SP into the address + 1.
|
|
67
|
+
#
|
|
68
|
+
def ld_mem_imm16_sp
|
|
69
|
+
imm16 = @cpu.fetch_next_word
|
|
70
|
+
sp = @registers.sp
|
|
71
|
+
|
|
72
|
+
@cpu.bus_write(address: imm16, value: sp & 0xFF)
|
|
73
|
+
@cpu.bus_write(address: imm16 + 1, value: (sp >> 8) & 0xFF)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Cpu
|
|
6
|
+
module Instructions
|
|
7
|
+
# Holds the logic of all the 8-bit Load instructions.
|
|
8
|
+
class Ld8 < Base
|
|
9
|
+
def initialize(cpu:, target:, source:)
|
|
10
|
+
super(cpu:)
|
|
11
|
+
|
|
12
|
+
@mnemonic = "LD #{format_operand(target)}, #{format_operand(source)}"
|
|
13
|
+
@logic = build_logic(target, source)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def build_logic(target, source)
|
|
19
|
+
return -> { ld_a_mem_imm16 } if source == :mem_imm16
|
|
20
|
+
return -> { ld_a_mem_bc } if source == :mem_bc
|
|
21
|
+
return -> { ld_a_mem_de } if source == :mem_de
|
|
22
|
+
|
|
23
|
+
case target
|
|
24
|
+
when :a then load_a(source)
|
|
25
|
+
when :b then load_b(source)
|
|
26
|
+
when :c then load_c(source)
|
|
27
|
+
when :d then load_d(source)
|
|
28
|
+
when :e then load_e(source)
|
|
29
|
+
when :h then load_h(source)
|
|
30
|
+
when :l then load_l(source)
|
|
31
|
+
when :mem_hl then load_mem_hl(source)
|
|
32
|
+
when :mem_hli then -> { load_mem_hli }
|
|
33
|
+
when :mem_hld then -> { load_mem_hld }
|
|
34
|
+
when :mem_bc then -> { @cpu.bus_write(address: @registers.bc, value: @registers.a) }
|
|
35
|
+
when :mem_de then -> { @cpu.bus_write(address: @registers.de, value: @registers.a) }
|
|
36
|
+
when :mem_imm16 then -> { ld_mem_imm16_a }
|
|
37
|
+
else
|
|
38
|
+
raise ArgumentError, 'Unknown target for :build_logic in Ld8'
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def load_a(source)
|
|
43
|
+
case source
|
|
44
|
+
when :a then -> {}
|
|
45
|
+
when :b then -> { @registers.a = @registers.b }
|
|
46
|
+
when :c then -> { @registers.a = @registers.c }
|
|
47
|
+
when :d then -> { @registers.a = @registers.d }
|
|
48
|
+
when :e then -> { @registers.a = @registers.e }
|
|
49
|
+
when :h then -> { @registers.a = @registers.h }
|
|
50
|
+
when :l then -> { @registers.a = @registers.l }
|
|
51
|
+
when :imm8 then -> { @registers.a = @cpu.fetch_next_byte }
|
|
52
|
+
when :mem_hl then -> { @registers.a = @cpu.bus_read(address: @registers.hl) }
|
|
53
|
+
when :mem_hli
|
|
54
|
+
lambda do
|
|
55
|
+
@registers.a = @cpu.bus_read(address: @registers.hl)
|
|
56
|
+
@registers.hl += 1
|
|
57
|
+
end
|
|
58
|
+
when :mem_hld
|
|
59
|
+
lambda do
|
|
60
|
+
@registers.a = @cpu.bus_read(address: @registers.hl)
|
|
61
|
+
@registers.hl -= 1
|
|
62
|
+
end
|
|
63
|
+
else
|
|
64
|
+
raise ArgumentError, 'Unknown source for :load_a in Ld8'
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def load_b(source)
|
|
69
|
+
case source
|
|
70
|
+
when :a then -> { @registers.b = @registers.a }
|
|
71
|
+
when :b then -> {}
|
|
72
|
+
when :c then -> { @registers.b = @registers.c }
|
|
73
|
+
when :d then -> { @registers.b = @registers.d }
|
|
74
|
+
when :e then -> { @registers.b = @registers.e }
|
|
75
|
+
when :h then -> { @registers.b = @registers.h }
|
|
76
|
+
when :l then -> { @registers.b = @registers.l }
|
|
77
|
+
when :imm8 then -> { @registers.b = @cpu.fetch_next_byte }
|
|
78
|
+
when :mem_hl then -> { @registers.b = @cpu.bus_read(address: @registers.hl) }
|
|
79
|
+
else
|
|
80
|
+
raise ArgumentError, 'Unknown source for :load_b in Ld8'
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def load_c(source)
|
|
85
|
+
case source
|
|
86
|
+
when :a then -> { @registers.c = @registers.a }
|
|
87
|
+
when :b then -> { @registers.c = @registers.b }
|
|
88
|
+
when :c then -> {}
|
|
89
|
+
when :d then -> { @registers.c = @registers.d }
|
|
90
|
+
when :e then -> { @registers.c = @registers.e }
|
|
91
|
+
when :h then -> { @registers.c = @registers.h }
|
|
92
|
+
when :l then -> { @registers.c = @registers.l }
|
|
93
|
+
when :imm8 then -> { @registers.c = @cpu.fetch_next_byte }
|
|
94
|
+
when :mem_hl then -> { @registers.c = @cpu.bus_read(address: @registers.hl) }
|
|
95
|
+
else
|
|
96
|
+
raise ArgumentError, 'Unknown source for :load_c in Ld8'
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def load_d(source)
|
|
101
|
+
case source
|
|
102
|
+
when :a then -> { @registers.d = @registers.a }
|
|
103
|
+
when :b then -> { @registers.d = @registers.b }
|
|
104
|
+
when :c then -> { @registers.d = @registers.c }
|
|
105
|
+
when :d then -> {}
|
|
106
|
+
when :e then -> { @registers.d = @registers.e }
|
|
107
|
+
when :h then -> { @registers.d = @registers.h }
|
|
108
|
+
when :l then -> { @registers.d = @registers.l }
|
|
109
|
+
when :imm8 then -> { @registers.d = @cpu.fetch_next_byte }
|
|
110
|
+
when :mem_hl then -> { @registers.d = @cpu.bus_read(address: @registers.hl) }
|
|
111
|
+
else
|
|
112
|
+
raise ArgumentError, 'Unknown source for :load_d in Ld8'
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def load_e(source)
|
|
117
|
+
case source
|
|
118
|
+
when :a then -> { @registers.e = @registers.a }
|
|
119
|
+
when :b then -> { @registers.e = @registers.b }
|
|
120
|
+
when :c then -> { @registers.e = @registers.c }
|
|
121
|
+
when :d then -> { @registers.e = @registers.d }
|
|
122
|
+
when :e then -> {}
|
|
123
|
+
when :h then -> { @registers.e = @registers.h }
|
|
124
|
+
when :l then -> { @registers.e = @registers.l }
|
|
125
|
+
when :imm8 then -> { @registers.e = @cpu.fetch_next_byte }
|
|
126
|
+
when :mem_hl then -> { @registers.e = @cpu.bus_read(address: @registers.hl) }
|
|
127
|
+
else
|
|
128
|
+
raise ArgumentError, 'Unknown source for :load_e in Ld8'
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def load_h(source)
|
|
133
|
+
case source
|
|
134
|
+
when :a then -> { @registers.h = @registers.a }
|
|
135
|
+
when :b then -> { @registers.h = @registers.b }
|
|
136
|
+
when :c then -> { @registers.h = @registers.c }
|
|
137
|
+
when :d then -> { @registers.h = @registers.d }
|
|
138
|
+
when :e then -> { @registers.h = @registers.e }
|
|
139
|
+
when :h then -> {}
|
|
140
|
+
when :l then -> { @registers.h = @registers.l }
|
|
141
|
+
when :imm8 then -> { @registers.h = @cpu.fetch_next_byte }
|
|
142
|
+
when :mem_hl then -> { @registers.h = @cpu.bus_read(address: @registers.hl) }
|
|
143
|
+
else
|
|
144
|
+
raise ArgumentError, 'Unknown source for :load_h in Ld8'
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def load_l(source)
|
|
149
|
+
case source
|
|
150
|
+
when :a then -> { @registers.l = @registers.a }
|
|
151
|
+
when :b then -> { @registers.l = @registers.b }
|
|
152
|
+
when :c then -> { @registers.l = @registers.c }
|
|
153
|
+
when :d then -> { @registers.l = @registers.d }
|
|
154
|
+
when :e then -> { @registers.l = @registers.e }
|
|
155
|
+
when :h then -> { @registers.l = @registers.h }
|
|
156
|
+
when :l then -> {}
|
|
157
|
+
when :imm8 then -> { @registers.l = @cpu.fetch_next_byte }
|
|
158
|
+
when :mem_hl then -> { @registers.l = @cpu.bus_read(address: @registers.hl) }
|
|
159
|
+
else
|
|
160
|
+
raise ArgumentError, 'Unknown source for :load_l in Ld8'
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def load_mem_hl(source)
|
|
165
|
+
case source
|
|
166
|
+
when :a then -> { @cpu.bus_write(address: @registers.hl, value: @registers.a) }
|
|
167
|
+
when :b then -> { @cpu.bus_write(address: @registers.hl, value: @registers.b) }
|
|
168
|
+
when :c then -> { @cpu.bus_write(address: @registers.hl, value: @registers.c) }
|
|
169
|
+
when :d then -> { @cpu.bus_write(address: @registers.hl, value: @registers.d) }
|
|
170
|
+
when :e then -> { @cpu.bus_write(address: @registers.hl, value: @registers.e) }
|
|
171
|
+
when :h then -> { @cpu.bus_write(address: @registers.hl, value: @registers.h) }
|
|
172
|
+
when :l then -> { @cpu.bus_write(address: @registers.hl, value: @registers.l) }
|
|
173
|
+
when :imm8 then -> { @cpu.bus_write(address: @registers.hl, value: @cpu.fetch_next_byte) }
|
|
174
|
+
else
|
|
175
|
+
raise ArgumentError, 'Unknown source for :load_mem_hl in Ld8'
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def load_mem_hli
|
|
180
|
+
@cpu.bus_write(address: @registers.hl, value: @registers.a)
|
|
181
|
+
@registers.hl += 1
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def load_mem_hld
|
|
185
|
+
@cpu.bus_write(address: @registers.hl, value: @registers.a)
|
|
186
|
+
@registers.hl -= 1
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def ld_mem_imm16_a
|
|
190
|
+
imm16_address = @cpu.fetch_next_word
|
|
191
|
+
@cpu.bus_write(address: imm16_address, value: @registers.a)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def ld_a_mem_imm16
|
|
195
|
+
imm16_address = @cpu.fetch_next_word
|
|
196
|
+
@registers.a = @cpu.bus_read(address: imm16_address)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def ld_a_mem_bc
|
|
200
|
+
@registers.a = @cpu.bus_read(address: @registers.bc)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def ld_a_mem_de
|
|
204
|
+
@registers.a = @cpu.bus_read(address: @registers.de)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Cpu
|
|
6
|
+
module Instructions
|
|
7
|
+
# Holds the logic of all the LDH (Load High RAM) instructions.
|
|
8
|
+
class Ldh < Base
|
|
9
|
+
IO_BASE_ADDRESS = 0xFF00
|
|
10
|
+
|
|
11
|
+
def initialize(cpu:, target:, source:)
|
|
12
|
+
super(cpu:)
|
|
13
|
+
|
|
14
|
+
@mnemonic = "LDH #{format_operand(target)}, #{format_operand(source)}"
|
|
15
|
+
@bytes = 2
|
|
16
|
+
@m_cycles = 2
|
|
17
|
+
@logic = build_logic(target, source)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def build_logic(target, source)
|
|
23
|
+
case target
|
|
24
|
+
when :a
|
|
25
|
+
case source
|
|
26
|
+
when :mem_unsig8 then -> { ldh_a_mem_unsig8 }
|
|
27
|
+
when :mem_c then -> { ldh_a_mem_c }
|
|
28
|
+
else
|
|
29
|
+
raise ArgumentError, 'Unknown Ldh source for :a target'
|
|
30
|
+
end
|
|
31
|
+
when :mem_unsig8 then -> { ldh_mem_unsig8_a }
|
|
32
|
+
when :mem_c then -> { ldh_mem_c_a }
|
|
33
|
+
else
|
|
34
|
+
raise ArgumentError, 'Unknown Ldh target'
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def ldh_mem_unsig8_a
|
|
39
|
+
unsigned_byte = @cpu.fetch_next_byte
|
|
40
|
+
|
|
41
|
+
@cpu.bus_write(address: IO_BASE_ADDRESS + unsigned_byte, value: @registers.a)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def ldh_a_mem_unsig8
|
|
45
|
+
unsigned_byte = @cpu.fetch_next_byte
|
|
46
|
+
|
|
47
|
+
@registers.a = @cpu.bus_read(address: IO_BASE_ADDRESS + unsigned_byte)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def ldh_mem_c_a
|
|
51
|
+
@cpu.bus_write(address: IO_BASE_ADDRESS + @registers.c, value: @registers.a)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def ldh_a_mem_c
|
|
55
|
+
@registers.a = @cpu.bus_read(address: IO_BASE_ADDRESS + @registers.c)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Cpu
|
|
6
|
+
module Instructions
|
|
7
|
+
# Holds the logic for CPL, SCF and CCF instructions.
|
|
8
|
+
class Misc < Base
|
|
9
|
+
def initialize(cpu:, operation:)
|
|
10
|
+
super(cpu:)
|
|
11
|
+
|
|
12
|
+
@mnemonic = format_operand(operation)
|
|
13
|
+
@logic = build_logic(operation)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def build_logic(operation)
|
|
19
|
+
case operation
|
|
20
|
+
when :cpl then -> { cpl }
|
|
21
|
+
when :scf then -> { scf }
|
|
22
|
+
when :ccf then -> { ccf }
|
|
23
|
+
else
|
|
24
|
+
raise ArgumentError, 'Unknown Misc operation'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Set Carry flag.
|
|
29
|
+
def scf
|
|
30
|
+
@registers.n_flag = false
|
|
31
|
+
@registers.h_flag = false
|
|
32
|
+
@registers.c_flag = true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Complement the Carry flag.
|
|
36
|
+
def ccf
|
|
37
|
+
@registers.n_flag = false
|
|
38
|
+
@registers.h_flag = false
|
|
39
|
+
@registers.c_flag = @registers.c_flag.zero?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Complement the value from the A register.
|
|
43
|
+
def cpl
|
|
44
|
+
@registers.a = ~@registers.a
|
|
45
|
+
|
|
46
|
+
@registers.n_flag = true
|
|
47
|
+
@registers.h_flag = true
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Cpu
|
|
6
|
+
module Instructions
|
|
7
|
+
# Defines the mnemonic, bytes and logic for the NOP instruction.
|
|
8
|
+
class Nop < Base
|
|
9
|
+
def initialize(cpu:)
|
|
10
|
+
super
|
|
11
|
+
|
|
12
|
+
@mnemonic = 'NOP'
|
|
13
|
+
@logic = -> {}
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Cpu
|
|
6
|
+
module Instructions
|
|
7
|
+
# Handles the logic related to all possible OR instructions
|
|
8
|
+
#
|
|
9
|
+
# - OR A, r8
|
|
10
|
+
# - OR A, [HL]
|
|
11
|
+
# - OR A, n8
|
|
12
|
+
class Or < Base
|
|
13
|
+
# @param cpu [Cpu] Holds a direct reference to the main Cpu object.
|
|
14
|
+
# @param source [Symbol] Operator, can be a register, :mem_hl, :imm8.
|
|
15
|
+
def initialize(cpu:, source:)
|
|
16
|
+
super(cpu:)
|
|
17
|
+
|
|
18
|
+
@mnemonic = "OR A, #{source}"
|
|
19
|
+
@logic = build_logic(source)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# Builds the logic for all OR instructions.
|
|
25
|
+
# Returns a lambda object to be called by the CPU.
|
|
26
|
+
def build_logic(source)
|
|
27
|
+
case source
|
|
28
|
+
when :a then -> { or_a(@registers.a) }
|
|
29
|
+
when :b then -> { or_a(@registers.b) }
|
|
30
|
+
when :c then -> { or_a(@registers.c) }
|
|
31
|
+
when :d then -> { or_a(@registers.d) }
|
|
32
|
+
when :e then -> { or_a(@registers.e) }
|
|
33
|
+
when :h then -> { or_a(@registers.h) }
|
|
34
|
+
when :l then -> { or_a(@registers.l) }
|
|
35
|
+
when :mem_hl then -> { or_a(@cpu.bus_read(address: @registers.hl)) }
|
|
36
|
+
when :imm8 then -> { or_a(@cpu.fetch_next_byte) }
|
|
37
|
+
else
|
|
38
|
+
raise ArgumentError, 'Unknown Or source'
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Performs a Bitwise OR between a given value and the A register.
|
|
43
|
+
#
|
|
44
|
+
def or_a(value)
|
|
45
|
+
result = @registers.a | value
|
|
46
|
+
|
|
47
|
+
@registers.clear_flags
|
|
48
|
+
@registers.z_flag = result.nobits?(0xFF)
|
|
49
|
+
|
|
50
|
+
@registers.a = result
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Cpu
|
|
6
|
+
module Instructions
|
|
7
|
+
# Holds the logic of all the POP instructions.
|
|
8
|
+
class Pop < Base
|
|
9
|
+
# Creates a Pop instruction object with a mnemonic and logic to be executed.
|
|
10
|
+
def initialize(cpu:, reg16:)
|
|
11
|
+
super(cpu:)
|
|
12
|
+
|
|
13
|
+
@mnemonic = "POP #{format_operand(reg16)}"
|
|
14
|
+
@logic = build_logic(reg16)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
# Returns a Proc object to be executed by the CPU at runtime.
|
|
20
|
+
#
|
|
21
|
+
# M-cycle 1: Fetches opcode.
|
|
22
|
+
# M-cycle 2: Gets the reg16 lsb from the stack.
|
|
23
|
+
# M-cycle 3: Gets the reg16 msb from the stack.
|
|
24
|
+
# Assigns the 16-bit value to a given reg16 (same cycle).
|
|
25
|
+
def build_logic(reg16)
|
|
26
|
+
case reg16
|
|
27
|
+
when :bc then -> { @registers.bc = @cpu.stack_pop }
|
|
28
|
+
when :de then -> { @registers.de = @cpu.stack_pop }
|
|
29
|
+
when :hl then -> { @registers.hl = @cpu.stack_pop }
|
|
30
|
+
when :af then -> { @registers.af = @cpu.stack_pop }
|
|
31
|
+
else
|
|
32
|
+
raise ArgumentError, 'Unknown Pop reg16'
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Cpu
|
|
6
|
+
module Instructions
|
|
7
|
+
# Holds the logic of all the PUSH instructions.
|
|
8
|
+
class Push < Base
|
|
9
|
+
# Creates a Push instruction object with a mnemonic and logic to be executed.
|
|
10
|
+
def initialize(cpu:, reg16:)
|
|
11
|
+
super(cpu:)
|
|
12
|
+
|
|
13
|
+
@mnemonic = "PUSH #{format_operand(reg16)}"
|
|
14
|
+
@logic = build_logic(reg16)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
# Returns a Proc object to be executed by the CPU at runtime.
|
|
20
|
+
def build_logic(reg16)
|
|
21
|
+
case reg16
|
|
22
|
+
when :bc then -> { push(@registers.bc) }
|
|
23
|
+
when :de then -> { push(@registers.de) }
|
|
24
|
+
when :hl then -> { push(@registers.hl) }
|
|
25
|
+
when :af then -> { push(@registers.af) }
|
|
26
|
+
else
|
|
27
|
+
raise ArgumentError, 'Unknown Push reg16'
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# M-cycle 1: Fetches opcode.
|
|
32
|
+
# M-cycle 2: Internal processing (dead cycle).
|
|
33
|
+
# M-cycle 3: Writes msb from reg16 into the stack.
|
|
34
|
+
# M-cycle 4: Writes lsb from reg16 into the stack.
|
|
35
|
+
def push(reg16)
|
|
36
|
+
@cpu.internal_processing
|
|
37
|
+
@cpu.stack_push(value: reg16)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Cpu
|
|
6
|
+
module Instructions
|
|
7
|
+
# Holds the logic of all the RET + RETI instructions.
|
|
8
|
+
class Ret < Base
|
|
9
|
+
# Creates a Ret instruction object with a mnemonic and logic to be executed.
|
|
10
|
+
def initialize(cpu:, condition: nil, enable_ime: false)
|
|
11
|
+
super(cpu:)
|
|
12
|
+
|
|
13
|
+
@mnemonic = enable_ime ? 'RETI' : "RET #{format_operand(condition)}".strip
|
|
14
|
+
@logic = build_logic(condition, enable_ime)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
# Returns a Proc object to be executed by the CPU at runtime.
|
|
20
|
+
def build_logic(condition, enable_ime)
|
|
21
|
+
return -> { reti } if enable_ime
|
|
22
|
+
return -> { ret } if condition.nil?
|
|
23
|
+
|
|
24
|
+
case condition
|
|
25
|
+
when :nz then -> { ret_cc(condition: @registers.z_flag.zero?) }
|
|
26
|
+
when :z then -> { ret_cc(condition: @registers.z_flag == 1) }
|
|
27
|
+
when :nc then -> { ret_cc(condition: @registers.c_flag.zero?) }
|
|
28
|
+
when :c then -> { ret_cc(condition: @registers.c_flag == 1) }
|
|
29
|
+
else
|
|
30
|
+
raise ArgumentError, 'Unknown Ret condition'
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# M-cycle 1: Fetches opcode.
|
|
35
|
+
# M-cycle 2: Spends 1 cycle to evaluate condition (Only for RET cc).
|
|
36
|
+
# ---------- Returns early if condition not met.
|
|
37
|
+
# M-cycle 3: Pops the LSB from the Stack.
|
|
38
|
+
# M-cycle 4: Pops the MSB from the Stack.
|
|
39
|
+
# M-cycle 5: Jumps to the return address.
|
|
40
|
+
def ret_cc(condition: true)
|
|
41
|
+
@cpu.internal_processing
|
|
42
|
+
return unless condition
|
|
43
|
+
|
|
44
|
+
return_address = @cpu.stack_pop
|
|
45
|
+
@cpu.jump_to(address: return_address)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# M-cycle 1: Fetches opcode.
|
|
49
|
+
# M-cycle 2: Pops the LSB from the Stack.
|
|
50
|
+
# M-cycle 3: Pops the MSB from the Stack.
|
|
51
|
+
# M-cycle 4: Jumps to the return address.
|
|
52
|
+
def ret
|
|
53
|
+
return_address = @cpu.stack_pop
|
|
54
|
+
@cpu.jump_to(address: return_address)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# M-cycle 1: Fetches opcode.
|
|
58
|
+
# M-cycle 2: Pops the LSB from the Stack.
|
|
59
|
+
# M-cycle 3: Pops the MSB from the Stack.
|
|
60
|
+
# M-cycle 4: Jumps to the return address
|
|
61
|
+
# Enable interrupts (same cycle).
|
|
62
|
+
def reti
|
|
63
|
+
ret
|
|
64
|
+
@cpu.enable_interrupts
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|