GBRb 0.1.0 → 0.2.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 +4 -4
- data/.gitignore +0 -1
- data/README.md +9 -1
- data/bin/gbrb +3 -3
- data/doc/images/blargg_cpu.png +0 -0
- data/doc/images/cpu_01.png +0 -0
- data/doc/images/cpu_03.png +0 -0
- data/doc/images/cpu_04.png +0 -0
- data/doc/images/cpu_05.png +0 -0
- data/doc/images/cpu_06.png +0 -0
- data/doc/images/cpu_07.png +0 -0
- data/doc/images/cpu_08.png +0 -0
- data/doc/images/cpu_09.png +0 -0
- data/doc/images/cpu_10.png +0 -0
- data/doc/images/cpu_11.png +0 -0
- data/doc/images/nintendo_logo.png +0 -0
- data/doc/images/opus5.png +0 -0
- data/doc/images/test.gb.png +0 -0
- data/doc/images/ttt.png +0 -0
- data/lib/gbrb.rb +7 -0
- data/lib/gbrb/cartridge.rb +21 -8
- data/lib/gbrb/cartridge/cartridge.rb +187 -0
- data/lib/gbrb/cpu/concatenated_register.rb +1 -1
- data/lib/gbrb/cpu/z80.rb +575 -498
- data/lib/gbrb/gb.rb +102 -32
- data/lib/gbrb/graphics.rb +1 -1
- data/lib/gbrb/graphics/gpu.rb +38 -30
- data/lib/gbrb/graphics/mode_clock.rb +31 -30
- data/lib/gbrb/graphics/screen_client.rb +3 -2
- data/lib/gbrb/instruction_set.rb +16 -0
- data/lib/gbrb/instruction_set/arithmetic.rb +238 -0
- data/lib/gbrb/instruction_set/bitwise.rb +64 -0
- data/lib/gbrb/instruction_set/boolean.rb +61 -0
- data/lib/gbrb/instruction_set/call.rb +40 -0
- data/lib/gbrb/instruction_set/carry.rb +23 -0
- data/lib/gbrb/instruction_set/cpl.rb +12 -0
- data/lib/gbrb/instruction_set/daa.rb +33 -0
- data/lib/gbrb/instruction_set/instruction.rb +23 -0
- data/lib/gbrb/instruction_set/jump.rb +47 -0
- data/lib/gbrb/instruction_set/ld.rb +241 -0
- data/lib/gbrb/instruction_set/return.rb +43 -0
- data/lib/gbrb/instruction_set/rotate.rb +178 -0
- data/lib/gbrb/instruction_set/rst.rb +16 -0
- data/lib/gbrb/instruction_set/special.rb +37 -0
- data/lib/gbrb/instruction_set/stack.rb +34 -0
- data/lib/gbrb/instruction_set/swap.rb +32 -0
- data/lib/gbrb/mmu.rb +60 -35
- data/lib/gbrb/options.rb +54 -0
- data/lib/gbrb/timer.rb +114 -0
- data/lib/gbrb/version.rb +1 -1
- data/misc/dump_diff +133 -0
- data/perf/cpu_perf_spec.rb +2 -2
- data/spec/gbrb/cartridge/cartridge_spec.rb +19 -0
- data/spec/gbrb/cartridge/mbc1_spec.rb +83 -0
- data/spec/gbrb/cpu/z80_spec.rb +92 -2
- data/spec/gbrb/{cpu/instruction_spec.rb → instruction_set/arithmetic_spec.rb} +21 -100
- data/spec/gbrb/instruction_set/boolean_spec.rb +50 -0
- data/spec/gbrb/instruction_set/instruction_spec.rb +22 -0
- data/spec/gbrb/instruction_set/ld_spec.rb +21 -0
- data/spec/gbrb/instruction_set/special_spec.rb +22 -0
- data/spec/gbrb/mmu_spec.rb +1 -1
- data/spec/gbrb/timer_spec.rb +26 -0
- metadata +53 -9
- data/lib/gbrb/cpu/instruction.rb +0 -648
- data/spec/gbrb/cartridge_spec.rb +0 -19
- data/spec/gbrb/graphics/mode_clock_spec.rb +0 -82
data/lib/gbrb/gb.rb
CHANGED
@@ -2,32 +2,46 @@ require_relative 'mmu'
|
|
2
2
|
require_relative 'cpu/z80'
|
3
3
|
require_relative 'graphics/gpu'
|
4
4
|
require_relative 'cartridge'
|
5
|
-
|
6
|
-
|
5
|
+
require_relative 'version'
|
6
|
+
require_relative 'timer'
|
7
7
|
|
8
8
|
module GBRb
|
9
9
|
class GB
|
10
|
-
def initialize
|
11
|
-
@
|
12
|
-
|
13
|
-
|
14
|
-
@gpu_receive = Queue.new
|
10
|
+
def initialize config
|
11
|
+
@config = config
|
12
|
+
display_about_info
|
13
|
+
display_separator
|
15
14
|
|
16
|
-
|
15
|
+
bios = @config.skip_boot ? [] : read_bios
|
16
|
+
@mmu = GBRb::MMU.new bios
|
17
17
|
@cpu = GBRb::CPU::Z80.new @mmu
|
18
|
-
@gpu = GBRb::Graphics::GPU.new
|
18
|
+
@gpu = GBRb::Graphics::GPU.new
|
19
|
+
@cartridge = GBRb::Cartridge.load_cartridge @config.cartridge_path
|
20
|
+
@timer = Timer.new
|
21
|
+
|
22
|
+
@mmu.connect @gpu, 0x8000..0x9fff
|
23
|
+
@mmu.connect @gpu, GBRb::Graphics::GPU::REGISTER_ADDRESSES
|
24
|
+
@mmu.connect @cartridge, 0x0000..0x7fff, 0xa000..0xbfff
|
25
|
+
@mmu.connect @timer, GBRb::Timer::ADDRESSES
|
26
|
+
|
27
|
+
@timer.irq_handler = @mmu
|
28
|
+
|
29
|
+
fake_boot_sequence if @config.skip_boot
|
19
30
|
end
|
20
31
|
|
21
32
|
def power_on
|
22
33
|
@power = :on
|
23
34
|
@gpu.power_on
|
24
|
-
@mode = @debug
|
35
|
+
@mode = @config.debug || :default
|
25
36
|
@breakpoint = nil
|
26
37
|
|
27
|
-
|
38
|
+
unless @mode == :default
|
39
|
+
puts debug_help if @mode == :interactive
|
40
|
+
puts " pc | sp | a | b | c | d | e | h | l | f | CB | OP Code |"
|
41
|
+
end
|
28
42
|
|
29
43
|
loop do
|
30
|
-
if @mode == :
|
44
|
+
if @mode == :interactive
|
31
45
|
new_mode = nil
|
32
46
|
until new_mode
|
33
47
|
new_mode = debug_input
|
@@ -37,19 +51,24 @@ module GBRb
|
|
37
51
|
|
38
52
|
pc = @cpu.r.pc.read
|
39
53
|
op_code = @mmu.read_byte(pc).to_i
|
40
|
-
prefix = @cpu.instance_eval '@cb_prefix'
|
41
54
|
|
42
55
|
begin
|
43
|
-
|
56
|
+
cycles = @cpu.step
|
57
|
+
@gpu.update! cycles
|
58
|
+
@timer.step cycles
|
44
59
|
rescue GBRb::CPU::InstructionNotImplemented => e
|
45
|
-
display_status
|
60
|
+
display_status @cpu.instance_eval('@cb_prefix'), op_code
|
46
61
|
@gpu.power_off
|
47
62
|
exit
|
48
63
|
end
|
49
64
|
|
50
|
-
@
|
51
|
-
if
|
52
|
-
|
65
|
+
prefix = @cpu.instance_eval '@cb_prefix'
|
66
|
+
op_code = @mmu.read_byte(pc + 1) if prefix
|
67
|
+
|
68
|
+
if @config.debug
|
69
|
+
if (@config.debug == :interactive) or (@config.debug == :dump and (op_code != 0xcb or prefix))
|
70
|
+
display_status prefix, op_code
|
71
|
+
end
|
53
72
|
if @step
|
54
73
|
@step -= 1
|
55
74
|
if @step == 0
|
@@ -59,15 +78,69 @@ module GBRb
|
|
59
78
|
end
|
60
79
|
if @cpu.r.pc.read == @breakpoint
|
61
80
|
@breakpoint = nil
|
62
|
-
@mode = :
|
81
|
+
@mode = :interactive
|
63
82
|
end
|
64
83
|
end
|
65
84
|
end
|
66
85
|
end
|
67
86
|
|
68
87
|
private
|
88
|
+
def display_about_info
|
89
|
+
puts "GBRb. v#{GBRb::VERSION}"
|
90
|
+
puts "http://rampantmonkey.github.io/GBRb/"
|
91
|
+
end
|
92
|
+
|
93
|
+
def display_separator
|
94
|
+
puts '*'*80
|
95
|
+
end
|
96
|
+
|
97
|
+
def fake_boot_sequence
|
98
|
+
@cpu.r.pc.store 0x100
|
99
|
+
@cpu.r.a.store 0x01
|
100
|
+
@cpu.r.f.store 0xb0
|
101
|
+
@cpu.r.b.store 0x00
|
102
|
+
@cpu.r.c.store 0x13
|
103
|
+
@cpu.r.d.store 0x00
|
104
|
+
@cpu.r.e.store 0xd8
|
105
|
+
@cpu.r.h.store 0x01
|
106
|
+
@cpu.r.l.store 0x4d
|
107
|
+
@cpu.r.sp.store 0xfffe
|
108
|
+
@mmu.write_byte 0xff05, 0x00
|
109
|
+
@mmu.write_byte 0xff06, 0x00
|
110
|
+
@mmu.write_byte 0xff07, 0x00
|
111
|
+
@mmu.write_byte 0xff10, 0x80
|
112
|
+
@mmu.write_byte 0xff11, 0xbf
|
113
|
+
@mmu.write_byte 0xff12, 0xf3
|
114
|
+
@mmu.write_byte 0xff14, 0xbf
|
115
|
+
@mmu.write_byte 0xff16, 0x3f
|
116
|
+
@mmu.write_byte 0xff17, 0x00
|
117
|
+
@mmu.write_byte 0xff19, 0xbf
|
118
|
+
@mmu.write_byte 0xff1a, 0x7f
|
119
|
+
@mmu.write_byte 0xff1b, 0xff
|
120
|
+
@mmu.write_byte 0xff1c, 0x9f
|
121
|
+
@mmu.write_byte 0xff1e, 0xbf
|
122
|
+
@mmu.write_byte 0xff20, 0xff
|
123
|
+
@mmu.write_byte 0xff21, 0x00
|
124
|
+
@mmu.write_byte 0xff22, 0x00
|
125
|
+
@mmu.write_byte 0xff23, 0xbf
|
126
|
+
@mmu.write_byte 0xff24, 0x77
|
127
|
+
@mmu.write_byte 0xff25, 0xf3
|
128
|
+
@mmu.write_byte 0xff26, 0xf1
|
129
|
+
@mmu.write_byte 0xff40, 0x91
|
130
|
+
@mmu.write_byte 0xff42, 0x00
|
131
|
+
@mmu.write_byte 0xff43, 0x00
|
132
|
+
@mmu.write_byte 0xff45, 0x00
|
133
|
+
@mmu.write_byte 0xff47, 0xfc
|
134
|
+
@mmu.write_byte 0xff48, 0xff
|
135
|
+
@mmu.write_byte 0xff49, 0xff
|
136
|
+
@mmu.write_byte 0xff4a, 0x00
|
137
|
+
@mmu.write_byte 0xff4b, 0x00
|
138
|
+
@mmu.write_byte 0xff50, 0x00
|
139
|
+
@mmu.write_byte 0xffff, 0x00
|
140
|
+
end
|
141
|
+
|
69
142
|
def debug_help
|
70
|
-
"Available modes: (b)reakpoint (h)elp (q)uit (s)tep (t)iles"
|
143
|
+
"Available debugging modes: (b)reakpoint (g)pu (h)elp (m)emory (q)uit (s)tep (t)iles"
|
71
144
|
end
|
72
145
|
|
73
146
|
def debug_input
|
@@ -76,6 +149,13 @@ module GBRb
|
|
76
149
|
when 'b'
|
77
150
|
@breakpoint = input[1..-1].to_i 16
|
78
151
|
:debug
|
152
|
+
when 'g'
|
153
|
+
command = input[1..-1].strip
|
154
|
+
begin
|
155
|
+
STDERR.puts @gpu.instance_eval(command)
|
156
|
+
rescue NameError
|
157
|
+
puts "#{command} is not a valid GPU operation"
|
158
|
+
end
|
79
159
|
when 'h'
|
80
160
|
STDERR.puts debug_help
|
81
161
|
when 'm'
|
@@ -91,13 +171,13 @@ module GBRb
|
|
91
171
|
when 's'
|
92
172
|
@step = input[1..-1].to_i
|
93
173
|
@step = 1 if @step == 0
|
94
|
-
:
|
174
|
+
:step
|
95
175
|
when 't'
|
96
176
|
@gpu.dump_tiles
|
97
177
|
nil
|
98
178
|
else
|
99
179
|
@step = 1
|
100
|
-
:
|
180
|
+
:interactive
|
101
181
|
end
|
102
182
|
end
|
103
183
|
|
@@ -110,16 +190,6 @@ module GBRb
|
|
110
190
|
def read_bios
|
111
191
|
File.read(File.expand_path('../bios', __FILE__)).split.map{ |el| el.to_i(16) }
|
112
192
|
end
|
113
|
-
|
114
|
-
def create_cartridge path
|
115
|
-
puts path
|
116
|
-
f = File.open(path, 'r')
|
117
|
-
c = Cartridge.new f
|
118
|
-
f.close
|
119
|
-
c
|
120
|
-
rescue Errno::ENOENT
|
121
|
-
raise InvalidCartridge, path
|
122
|
-
end
|
123
193
|
end
|
124
194
|
end
|
125
195
|
|
data/lib/gbrb/graphics.rb
CHANGED
data/lib/gbrb/graphics/gpu.rb
CHANGED
@@ -16,18 +16,21 @@ module GBRb::Graphics
|
|
16
16
|
TILE_MAP_0 = 0x9800
|
17
17
|
TILE_MAP_1 = 0x9c00
|
18
18
|
TILE_SET_START = 0x8000
|
19
|
+
MEMORY_SIZE = 0x2000
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@outgoing = outgoing
|
21
|
+
REGISTER_ADDRESSES = [CONTROL_REGISTER, SCROLL_Y_ADDRESS, SCROLL_X_ADDRESS, LINE_ADDRESS, PALETTE_REGISTER]
|
22
|
+
|
23
|
+
def initialize
|
24
24
|
reset
|
25
25
|
end
|
26
26
|
|
27
27
|
def reset
|
28
|
+
@memory = Array.new MEMORY_SIZE, 0
|
29
|
+
@registers = REGISTER_ADDRESSES.inject({}){|acc, add| acc[add] = 0; acc}
|
28
30
|
super
|
29
31
|
create_frame_buffer
|
30
32
|
create_tiles
|
33
|
+
update_palette
|
31
34
|
end
|
32
35
|
|
33
36
|
def create_tiles
|
@@ -41,12 +44,30 @@ module GBRb::Graphics
|
|
41
44
|
end
|
42
45
|
end
|
43
46
|
|
47
|
+
def read_byte address
|
48
|
+
if REGISTER_ADDRESSES.include? address
|
49
|
+
@registers[address]
|
50
|
+
else
|
51
|
+
@memory[address & (MEMORY_SIZE - 1)]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_byte address, value
|
56
|
+
if REGISTER_ADDRESSES.include? address
|
57
|
+
@registers[address] = value & 0xff
|
58
|
+
update_palette if address == PALETTE_REGISTER
|
59
|
+
else
|
60
|
+
@memory[address & (MEMORY_SIZE - 1)] = value & 0xff
|
61
|
+
update_tile address
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
44
65
|
def update_tile address
|
45
66
|
tile = ((address & 0x1ffe) >> 4) & 0x1ff
|
46
67
|
y = ((address & 0x1ffe) >> 1) & 0x07
|
47
68
|
8.times do |x|
|
48
69
|
x_mask = 1 << (7-x)
|
49
|
-
@tiles[tile][y][x] = ((
|
70
|
+
@tiles[tile][y][x] = ((read_byte(address-1) & x_mask) == x_mask ? 1 : 0) + ((read_byte(address) & x_mask) == x_mask ? 2 : 0)
|
50
71
|
end
|
51
72
|
end
|
52
73
|
|
@@ -57,20 +78,7 @@ module GBRb::Graphics
|
|
57
78
|
def power_on
|
58
79
|
trap(:CHLD) { exit }
|
59
80
|
@screen_pid = fork { start_screen }
|
60
|
-
|
61
|
-
Thread.abort_on_exception = true
|
62
|
-
Thread.new do
|
63
|
-
loop do
|
64
|
-
message = @incoming.pop
|
65
|
-
case message.first
|
66
|
-
when :cpu
|
67
|
-
self.update! message.last.to_i if display_on?
|
68
|
-
@outgoing.push :nothing
|
69
|
-
when :mmu
|
70
|
-
self.update_tile message.last.to_i
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
81
|
+
screen.write @frame_buffer.map{|row| row.map{|pixel| @palette[pixel]}}
|
74
82
|
end
|
75
83
|
|
76
84
|
def power_off
|
@@ -93,7 +101,7 @@ module GBRb::Graphics
|
|
93
101
|
row = []
|
94
102
|
WIDTH.times do
|
95
103
|
palette_index = @tiles[tile_index][tile_y][tile_x]
|
96
|
-
color = palette[palette_index]
|
104
|
+
color = @palette[palette_index]
|
97
105
|
row << color
|
98
106
|
tile_x += 1
|
99
107
|
if tile_x == 0x08
|
@@ -106,19 +114,19 @@ module GBRb::Graphics
|
|
106
114
|
end
|
107
115
|
|
108
116
|
def calculate_tile_number tilemap_offset, line_offset
|
109
|
-
id =
|
117
|
+
id = read_byte(tilemap_offset + line_offset)
|
110
118
|
id += 256 if background_tile_set == 0x10 and id < 128
|
111
119
|
id
|
112
120
|
end
|
113
121
|
|
114
|
-
def
|
115
|
-
value =
|
122
|
+
def update_palette
|
123
|
+
value = read_byte PALETTE_REGISTER
|
116
124
|
colors = []
|
117
125
|
4.times do |i|
|
118
126
|
mask = 0b11 << 2*i
|
119
|
-
colors << ((
|
127
|
+
colors << ((value & mask) >> 2*i)
|
120
128
|
end
|
121
|
-
colors
|
129
|
+
@palette = colors
|
122
130
|
end
|
123
131
|
|
124
132
|
def dump_tiles
|
@@ -130,23 +138,23 @@ module GBRb::Graphics
|
|
130
138
|
end
|
131
139
|
|
132
140
|
def line
|
133
|
-
|
141
|
+
read_byte LINE_ADDRESS
|
134
142
|
end
|
135
143
|
|
136
144
|
def line= value
|
137
|
-
@
|
145
|
+
@registers[LINE_ADDRESS] = value & 0xff
|
138
146
|
end
|
139
147
|
|
140
148
|
def scroll_y
|
141
|
-
|
149
|
+
read_byte SCROLL_Y_ADDRESS
|
142
150
|
end
|
143
151
|
|
144
152
|
def scroll_x
|
145
|
-
|
153
|
+
read_byte SCROLL_X_ADDRESS
|
146
154
|
end
|
147
155
|
|
148
156
|
def control_register
|
149
|
-
|
157
|
+
read_byte CONTROL_REGISTER
|
150
158
|
end
|
151
159
|
|
152
160
|
def background_on?
|
@@ -6,44 +6,45 @@ module GBRb::Graphics
|
|
6
6
|
|
7
7
|
def reset
|
8
8
|
@clock = 0
|
9
|
-
@mode =
|
9
|
+
@mode = 0
|
10
10
|
self.line = 0
|
11
11
|
end
|
12
12
|
|
13
13
|
def update! time_increment
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
unless self.display_on?
|
15
|
+
self.line = 0
|
16
|
+
@clock = 456
|
17
|
+
@mode = 0
|
18
|
+
return
|
19
|
+
else
|
20
|
+
if line >= 144
|
21
|
+
@mode = 1
|
22
|
+
elsif @clock >= 456 - 80
|
23
|
+
@mode = 2
|
24
|
+
elsif @clock >= 456 - 80 - 172
|
20
25
|
@mode = 3
|
21
|
-
|
22
|
-
when 3
|
23
|
-
if @clock >= 172
|
24
|
-
@clock = 0
|
26
|
+
else
|
25
27
|
@mode = 0
|
26
|
-
render_scan_line
|
27
28
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
@clock -= time_increment
|
33
|
+
|
34
|
+
if @clock <= 0
|
35
|
+
@clock += 456
|
36
|
+
self.line = line + 1
|
37
|
+
|
38
|
+
if line == 144
|
39
|
+
self.draw_frame_buffer
|
40
|
+
elsif line > 153
|
41
|
+
self.line = 0
|
38
42
|
end
|
39
|
-
|
40
|
-
if
|
41
|
-
self.
|
42
|
-
|
43
|
-
|
44
|
-
self.line = 0
|
45
|
-
@mode = 2
|
46
|
-
end
|
43
|
+
|
44
|
+
if line < 144
|
45
|
+
if self.display_on?
|
46
|
+
render_scan_line
|
47
|
+
end
|
47
48
|
end
|
48
49
|
end
|
49
50
|
end
|
@@ -3,11 +3,12 @@ require_relative '../graphics'
|
|
3
3
|
require 'socket'
|
4
4
|
|
5
5
|
module GBRb::Graphics
|
6
|
+
PPM_PALETTE = [0xeb, 0xc4, 0x60, 0x00]
|
6
7
|
class ScreenClient
|
7
8
|
def initialize
|
8
9
|
trap(:PIPE) { exit }
|
9
10
|
@connection = UNIXSocket.new SOCKET_PATH
|
10
|
-
rescue Errno::ECONNREFUSED
|
11
|
+
rescue Errno::ECONNREFUSED, Errno::ENOENT
|
11
12
|
sleep 0.1
|
12
13
|
retry
|
13
14
|
end
|
@@ -22,7 +23,7 @@ module GBRb::Graphics
|
|
22
23
|
d = "P6 #{WIDTH} #{HEIGHT} #{MAX_COLOR} "
|
23
24
|
data.each do |row|
|
24
25
|
row.each do |pixel|
|
25
|
-
d << pixel.chr * 3
|
26
|
+
d << PPM_PALETTE[pixel].chr * 3
|
26
27
|
end
|
27
28
|
end
|
28
29
|
d << "\n"
|