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,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
|