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,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Amaterasu
4
+ module GameBoy
5
+ class Ppu
6
+ # Models the behavior of the PPU registers.
7
+ class Registers
8
+ attr_reader :lcdc,
9
+ :stat,
10
+ :scy,
11
+ :scx,
12
+ :ly,
13
+ :lyc,
14
+ :bgp,
15
+ :obp0,
16
+ :obp1,
17
+ :wy,
18
+ :wx,
19
+ :bg_palettes,
20
+ :sprite_palettes0,
21
+ :sprite_palettes1
22
+
23
+ # @param skip_boot_rom [Boolean]
24
+ def initialize(interrupts, skip_boot_rom: true)
25
+ @lcdc = LcdControl.new(skip_boot_rom:)
26
+ @stat = LcdStatus.new(interrupts, skip_boot_rom:)
27
+ @scy = 0x00
28
+ @scx = 0x00
29
+ @ly = 0x00
30
+ @lyc = 0x00
31
+ @bgp = skip_boot_rom ? 0xFC : 0x00
32
+ @obp0 = 0x00
33
+ @obp1 = 0x00
34
+ @wy = 0x00
35
+ @wx = 0x00
36
+
37
+ @bg_palettes = [
38
+ @bgp & 0b11,
39
+ (@bgp >> 2) & 0b11,
40
+ (@bgp >> 4) & 0b11,
41
+ (@bgp >> 6) & 0b11
42
+ ]
43
+
44
+ @sprite_palettes0 = [
45
+ @obp0 & 0b11,
46
+ (@obp0 >> 2) & 0b11,
47
+ (@obp0 >> 4) & 0b11,
48
+ (@obp0 >> 6) & 0b11
49
+ ]
50
+
51
+ @sprite_palettes1 = [
52
+ @obp1 & 0b11,
53
+ (@obp1 >> 2) & 0b11,
54
+ (@obp1 >> 4) & 0b11,
55
+ (@obp1 >> 6) & 0b11
56
+ ]
57
+ end
58
+
59
+ # @param value [Integer]
60
+ def lcdc=(value)
61
+ @lcdc.value = value
62
+ end
63
+
64
+ # @param value [Integer]
65
+ def stat=(value)
66
+ @stat.value = value
67
+ end
68
+
69
+ # @param value [Integer]
70
+ def scy=(value)
71
+ @scy = value & 0xFF
72
+ end
73
+
74
+ # @param value [Integer]
75
+ def scx=(value)
76
+ @scx = value & 0xFF
77
+ end
78
+
79
+ # @param value [Integer]
80
+ def ly=(value)
81
+ @ly = value & 0xFF
82
+ end
83
+
84
+ # @param value [Integer]
85
+ def lyc=(value)
86
+ @lyc = value & 0xFF
87
+ end
88
+
89
+ # @param value [Integer]
90
+ def bgp=(value)
91
+ @bgp = value & 0xFF
92
+
93
+ @bg_palettes[0] = @bgp & 0b11
94
+ @bg_palettes[1] = (@bgp >> 2) & 0b11
95
+ @bg_palettes[2] = (@bgp >> 4) & 0b11
96
+ @bg_palettes[3] = (@bgp >> 6) & 0b11
97
+ end
98
+
99
+ # @param value [Integer]
100
+ def obp0=(value)
101
+ @obp0 = value & 0xFF
102
+
103
+ @sprite_palettes0[0] = @obp0 & 0b11
104
+ @sprite_palettes0[1] = (@obp0 >> 2) & 0b11
105
+ @sprite_palettes0[2] = (@obp0 >> 4) & 0b11
106
+ @sprite_palettes0[3] = (@obp0 >> 6) & 0b11
107
+ end
108
+
109
+ # @param value [Integer]
110
+ def obp1=(value)
111
+ @obp1 = value & 0xFF
112
+
113
+ @sprite_palettes1[0] = @obp1 & 0b11
114
+ @sprite_palettes1[1] = (@obp1 >> 2) & 0b11
115
+ @sprite_palettes1[2] = (@obp1 >> 4) & 0b11
116
+ @sprite_palettes1[3] = (@obp1 >> 6) & 0b11
117
+ end
118
+
119
+ # @param value [Integer]
120
+ def wy=(value)
121
+ @wy = value & 0xFF
122
+ end
123
+
124
+ # @param value [Integer]
125
+ def wx=(value)
126
+ @wx = value & 0xFF
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Amaterasu
4
+ module GameBoy
5
+ # This class models the Pixel Processing Unit from the Original Game Boy.
6
+ #
7
+ # The Ppu outputs a 160x144 pixel framebuffer each frame.
8
+ # This framebuffer will be used by the chosen Renderer to display the graphics.
9
+ # The Ppu should not care about how the pixels are rendered, just output them.
10
+ #
11
+ # Specifications:
12
+ # - The frame consists of 154 scanlines, 144 visible + 10 vblank (Cpu can access VRAM).
13
+ # - Scanlines are drawn from top to bottom, left to right.
14
+ # - Updating registers mid-frame can cause effects since the pixels are still being drawn.
15
+ # - Dots per scanline = 456 t-cycles (114 m-cycles) -> Hardware spec.
16
+ # - Dots per frame = 154 * 456 = 70_224 t-cycles (17_556 m-cycles).
17
+ # - OAM search takes 80 dots (20 m-cycles) -> 40 sprites, 2 dots each.
18
+ class Ppu
19
+ include Utils::BitOps
20
+
21
+ PIXELS_PER_SCANLINE = 160
22
+ VISIBLE_SCANLINES = 144
23
+ TOTAL_SCANLINES = 154
24
+ DOTS_PER_SCANLINE = 456
25
+ MAX_SPRITES_PER_SCANLINE = 10
26
+
27
+ attr_accessor :wy_eq_ly, :window_y_count
28
+
29
+ attr_reader :registers,
30
+ :dots,
31
+ :framebuffer,
32
+ :sprite_buffer
33
+
34
+ def initialize(
35
+ vram,
36
+ oam,
37
+ display,
38
+ interrupts,
39
+ skip_boot_rom: true,
40
+ trace_ppu: false
41
+ )
42
+ @vram = vram
43
+ @oam = oam
44
+ @display = display
45
+ @interrupts = interrupts
46
+ @trace_ppu = trace_ppu
47
+
48
+ @registers = Registers.new(interrupts, skip_boot_rom:)
49
+ @framebuffer = Array.new
50
+ @sprite_buffer = Array.new(MAX_SPRITES_PER_SCANLINE).clear
51
+
52
+ @modes = Modes.build_hash(self)
53
+ @mode = set_mode(:disabled)
54
+ @wy_eq_ly = false
55
+ @window_y_count = 0
56
+ @dots = 0
57
+ end
58
+
59
+ # Core PPU state machine.
60
+ #
61
+ # Each mode is responsible for its own logic and
62
+ # also switching to the next mode.
63
+ def tick
64
+ @mode.tick
65
+ log_state if @trace_ppu
66
+ @dots += 1
67
+ end
68
+
69
+ # Sets the current PPU mode to be ticked.
70
+ # Updates STAT Bits 1-0 that reads the current PPU mode.
71
+ #
72
+ # @param mode [Symbol]
73
+ def set_mode(mode)
74
+ @mode = @modes[mode]
75
+ @registers.stat.set_mode_bits(@mode.number)
76
+ # request_interrupt(:lcd_stat) if @registers.stat.rising_edge?
77
+
78
+ @mode
79
+ end
80
+
81
+ def reset_for_scanline
82
+ @dots = 0
83
+ @sprite_buffer.clear unless @sprite_buffer.empty?
84
+ end
85
+
86
+ # Restarts the rendering pipeline state.
87
+ def reset_states
88
+ reset_for_scanline
89
+ @registers.ly = 0x00
90
+ @wy_eq_ly = false # here?
91
+ @window_y_count = 0
92
+ @framebuffer.clear
93
+
94
+ ly_compare
95
+ end
96
+
97
+ # Delegates the draw to the chosen Renderer.
98
+ def draw_frame
99
+ @display&.draw(@framebuffer)
100
+ end
101
+
102
+ def request_interrupt(interrupt_type)
103
+ @interrupts.request(interrupt_type)
104
+ end
105
+
106
+ def increment_ly
107
+ @registers.ly += 1
108
+
109
+ ly_compare
110
+ end
111
+
112
+ def ly_compare
113
+ if @registers.ly == @registers.lyc
114
+ @registers.stat.set_lyc_bit
115
+ else
116
+ @registers.stat.clear_lyc_bit
117
+ end
118
+
119
+ # request_interrupt(:lcd_stat) if @registers.stat.rising_edge?
120
+ end
121
+
122
+ # Returns a 8-bit value stored in VRAM in a given address.
123
+ #
124
+ # @param address [Integer]
125
+ # @return [Integer]
126
+ def read_vram(address:)
127
+ return 0xFF if @mode == @modes[:rendering]
128
+
129
+ @vram.read_byte(address:)
130
+ end
131
+
132
+ # Stores a 8-bit value in VRAM in a given address.
133
+ #
134
+ # @param address [Integer]
135
+ # @param value [Integer]
136
+ def write_vram(address:, value:)
137
+ @vram.write_byte(address:, value:)
138
+ end
139
+
140
+ # Returns a 8-bit value stored in OAM in a given address.
141
+ def read_oam(address:)
142
+ return 0xFF if [@modes[:oam_scan], @modes[:rendering]].include?(@mode)
143
+
144
+ @oam.read_byte(address:)
145
+ end
146
+
147
+ # Stores a 8-bit value in OAM in a given address.
148
+ def write_oam(address:, value:)
149
+ @oam.write_byte(address:, value:)
150
+ end
151
+
152
+ def fetch_sprite_at(index)
153
+ @oam.sprite(index)
154
+ end
155
+
156
+ # @return [TileMap]
157
+ def window_tile_map
158
+ return @vram.tile_map_high if @registers.lcdc.window_tile_map_high?
159
+
160
+ @vram.tile_map_low
161
+ end
162
+
163
+ # @return [TileMap]
164
+ def bg_tile_map
165
+ return @vram.tile_map_high if @registers.lcdc.bg_tile_map_high?
166
+
167
+ @vram.tile_map_low
168
+ end
169
+
170
+ # @return [TileData]
171
+ def bg_win_tile_data
172
+ @vram.tile_data.addressing_mode =
173
+ if @registers.lcdc.tile_data_at_0x8000?
174
+ :unsigned
175
+ else
176
+ :signed
177
+ end
178
+
179
+ @vram.tile_data
180
+ end
181
+
182
+ # @return [TileData]
183
+ def obj_tile_data
184
+ @vram.tile_data.addressing_mode = :unsigned
185
+ @vram.tile_data
186
+ end
187
+
188
+ private
189
+
190
+ # Prints the current state of the PPU into the console for debugging.
191
+ def log_state
192
+ $stdout.printf(
193
+ '#%<dots>03d | ' \
194
+ 'LY: $%<ly>02X (%<ly>d) | ' \
195
+ 'LCDC: $%<lcdc>02X | ' \
196
+ 'STAT: $%<stat>02X | ' \
197
+ "MODE: %<mode>s\n",
198
+ dots: @dots,
199
+ ly: @registers.ly,
200
+ lcdc: @registers.lcdc.value,
201
+ stat: @registers.stat.value,
202
+ mode: @mode.to_s
203
+ )
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Amaterasu
4
+ module GameBoy
5
+ # Models the RAM chip within the Game Boy.
6
+ class Ram
7
+ # Memory size in bytes.
8
+ attr_reader :size
9
+
10
+ # Creates a new Ram object.
11
+ #
12
+ # @param size [Integer] Memory size in bytes.
13
+ # @param offset [Integer] Address offset based on the Bus memory map.
14
+ def initialize(size:, offset:)
15
+ @size = size
16
+ @offset = offset
17
+
18
+ @data = Array.new(size, 0x00)
19
+ @backup = nil
20
+ @backed_up = false
21
+ end
22
+
23
+ # Returns a 8-bit value from a given address in memory.
24
+ def read_byte(address:)
25
+ @data[address - @offset]
26
+ end
27
+
28
+ # Stores a 8-bit value into a given address in memory.
29
+ def write_byte(address:, value:)
30
+ @data[address - @offset] = value & 0xFF
31
+ end
32
+
33
+ # Provides an interface to read backup data.
34
+ def read_backup(address:)
35
+ return 0xFF unless @backed_up
36
+
37
+ @backup[address - @offset]
38
+ end
39
+
40
+ # Returns a readable string in B or KiB based on memory size.
41
+ def disk_size
42
+ return "#{@size} B" if @size < 1024
43
+
44
+ "#{@size / 1024} KiB"
45
+ end
46
+
47
+ # Creates a copy of the current state of memory.
48
+ def save_data
49
+ @backup = @data.dup
50
+ @backed_up = true
51
+ end
52
+
53
+ # Restores a previously backed up memory state.
54
+ def restore_data
55
+ @data = @backup.dup
56
+ end
57
+
58
+ # Clears all previously set memory values.
59
+ def wipe_data
60
+ @data = Array.new(@size, 0x00)
61
+ end
62
+
63
+ # Clears all previously set backup values.
64
+ def wipe_backup
65
+ @backup = Array.new
66
+ @backed_up = false
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Amaterasu
4
+ module GameBoy
5
+ # Models the Serial Port present in the Game Boy.
6
+ #
7
+ # - Very useful for performing community accuracy tests, they use the serial to output results.
8
+ class Serial
9
+ # Returns the 8-bit value stored in the SB (Serial Transfer Data) register.
10
+ attr_reader :sb
11
+
12
+ # Returns an Array with all the bytes from the serial transfer.
13
+ attr_reader :message_buffer
14
+
15
+ # Creates a serial port instance.
16
+ #
17
+ # - Needs to hold an instance of interrupts to request a :serial interrupt.
18
+ # - Holds the state of the Transfer Data and Control registers.
19
+ def initialize(interrupts, trace_serial: false)
20
+ @interrupts = interrupts
21
+ @trace_serial = trace_serial
22
+
23
+ @sb = 0x00
24
+ @sc = 0xFF
25
+
26
+ @message_buffer = Array.new
27
+ end
28
+
29
+ # Returns the 8-bit value stored in the SC (Serial Transfer Control) register.
30
+ #
31
+ # - In the actual hardware only Bit 7 and Bit 0 are wired.
32
+ # - Bits 6-1 always return 1 when read.
33
+ def sc
34
+ @sc | 0b01111110
35
+ end
36
+
37
+ # Sets a 8-bit value into the SB register.
38
+ def sb=(value)
39
+ @sb = value & 0xFF
40
+ end
41
+
42
+ # Sets a 8-bit value into the SC register.
43
+ #
44
+ # - Bits 6-1 are ignored since they are not wired to anything.
45
+ # - If bit 7 goes from 0 -> 1, a transfer is started.
46
+ def sc=(value)
47
+ @sc = value & 0b10000001
48
+
49
+ start_transfer if transfer_enabled?
50
+ end
51
+
52
+ private
53
+
54
+ # Bit 7 from the SC register determines if the transfer is enabled.
55
+ def transfer_enabled?
56
+ (@sc >> 7).allbits?(1)
57
+ end
58
+
59
+ # Bit 0 from the SC register determines which clock controls the transfer.
60
+ #
61
+ # - If Bit 0 is set, the internal clock from the receiving Game Boy controls the transfer.
62
+ # - If Bit 0 is cleared, the Game Boy waits for an external clock pulse.
63
+ def clock_bit
64
+ @sc & 1
65
+ end
66
+
67
+ # Transfers the byte from the SB register.
68
+ #
69
+ # - For the transfer to actually begin bits 7 and 0 need to be set.
70
+ # - Internal clock must be selected, otherwise it just waits.
71
+ def start_transfer
72
+ return if clock_bit.zero?
73
+
74
+ @message_buffer << @sb
75
+ @sb = 0xFF
76
+
77
+ complete_transfer
78
+ end
79
+
80
+ # Completes the transfer immediately after.
81
+ #
82
+ # - Transfer enable flag (Bit 7) is cleared.
83
+ # - Requests a :serial interrupt.
84
+ def complete_transfer
85
+ @sc &= 0b01111111
86
+ puts @message_buffer.pack('C*') if @trace_serial
87
+ @interrupts.request(:serial)
88
+ end
89
+ end
90
+ end
91
+ end