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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/README.md +9 -1
  4. data/bin/gbrb +3 -3
  5. data/doc/images/blargg_cpu.png +0 -0
  6. data/doc/images/cpu_01.png +0 -0
  7. data/doc/images/cpu_03.png +0 -0
  8. data/doc/images/cpu_04.png +0 -0
  9. data/doc/images/cpu_05.png +0 -0
  10. data/doc/images/cpu_06.png +0 -0
  11. data/doc/images/cpu_07.png +0 -0
  12. data/doc/images/cpu_08.png +0 -0
  13. data/doc/images/cpu_09.png +0 -0
  14. data/doc/images/cpu_10.png +0 -0
  15. data/doc/images/cpu_11.png +0 -0
  16. data/doc/images/nintendo_logo.png +0 -0
  17. data/doc/images/opus5.png +0 -0
  18. data/doc/images/test.gb.png +0 -0
  19. data/doc/images/ttt.png +0 -0
  20. data/lib/gbrb.rb +7 -0
  21. data/lib/gbrb/cartridge.rb +21 -8
  22. data/lib/gbrb/cartridge/cartridge.rb +187 -0
  23. data/lib/gbrb/cpu/concatenated_register.rb +1 -1
  24. data/lib/gbrb/cpu/z80.rb +575 -498
  25. data/lib/gbrb/gb.rb +102 -32
  26. data/lib/gbrb/graphics.rb +1 -1
  27. data/lib/gbrb/graphics/gpu.rb +38 -30
  28. data/lib/gbrb/graphics/mode_clock.rb +31 -30
  29. data/lib/gbrb/graphics/screen_client.rb +3 -2
  30. data/lib/gbrb/instruction_set.rb +16 -0
  31. data/lib/gbrb/instruction_set/arithmetic.rb +238 -0
  32. data/lib/gbrb/instruction_set/bitwise.rb +64 -0
  33. data/lib/gbrb/instruction_set/boolean.rb +61 -0
  34. data/lib/gbrb/instruction_set/call.rb +40 -0
  35. data/lib/gbrb/instruction_set/carry.rb +23 -0
  36. data/lib/gbrb/instruction_set/cpl.rb +12 -0
  37. data/lib/gbrb/instruction_set/daa.rb +33 -0
  38. data/lib/gbrb/instruction_set/instruction.rb +23 -0
  39. data/lib/gbrb/instruction_set/jump.rb +47 -0
  40. data/lib/gbrb/instruction_set/ld.rb +241 -0
  41. data/lib/gbrb/instruction_set/return.rb +43 -0
  42. data/lib/gbrb/instruction_set/rotate.rb +178 -0
  43. data/lib/gbrb/instruction_set/rst.rb +16 -0
  44. data/lib/gbrb/instruction_set/special.rb +37 -0
  45. data/lib/gbrb/instruction_set/stack.rb +34 -0
  46. data/lib/gbrb/instruction_set/swap.rb +32 -0
  47. data/lib/gbrb/mmu.rb +60 -35
  48. data/lib/gbrb/options.rb +54 -0
  49. data/lib/gbrb/timer.rb +114 -0
  50. data/lib/gbrb/version.rb +1 -1
  51. data/misc/dump_diff +133 -0
  52. data/perf/cpu_perf_spec.rb +2 -2
  53. data/spec/gbrb/cartridge/cartridge_spec.rb +19 -0
  54. data/spec/gbrb/cartridge/mbc1_spec.rb +83 -0
  55. data/spec/gbrb/cpu/z80_spec.rb +92 -2
  56. data/spec/gbrb/{cpu/instruction_spec.rb → instruction_set/arithmetic_spec.rb} +21 -100
  57. data/spec/gbrb/instruction_set/boolean_spec.rb +50 -0
  58. data/spec/gbrb/instruction_set/instruction_spec.rb +22 -0
  59. data/spec/gbrb/instruction_set/ld_spec.rb +21 -0
  60. data/spec/gbrb/instruction_set/special_spec.rb +22 -0
  61. data/spec/gbrb/mmu_spec.rb +1 -1
  62. data/spec/gbrb/timer_spec.rb +26 -0
  63. metadata +53 -9
  64. data/lib/gbrb/cpu/instruction.rb +0 -648
  65. data/spec/gbrb/cartridge_spec.rb +0 -19
  66. data/spec/gbrb/graphics/mode_clock_spec.rb +0 -82
@@ -0,0 +1,16 @@
1
+ require_relative 'instruction'
2
+
3
+ module GBRb::InstructionSet
4
+ class Rst < Instruction
5
+ def initialize offset
6
+ @offset = offset
7
+ super 1, 16
8
+ end
9
+
10
+ def call r, mem
11
+ r.sp.store r.sp.read - 2
12
+ mem.write_word r.sp.read, r.pc.read
13
+ r.pc.store 0x0000 + @offset
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'instruction'
2
+ require_relative '../../gbrb'
3
+
4
+ module GBRb::InstructionSet
5
+ class Stop < Instruction; end
6
+
7
+ class EnableInterrupts < Instruction
8
+ def initialize
9
+ super 1, 4, -1
10
+ end
11
+
12
+ def call cpu
13
+ cpu.interrupts = :enabled
14
+ end
15
+ end
16
+
17
+ class DisableInterrupts < Instruction
18
+ def initialize
19
+ super 1, 4, -1
20
+ end
21
+
22
+ def call cpu
23
+ cpu.interrupts = :disabled
24
+ end
25
+ end
26
+
27
+ class Halt < Instruction
28
+ def initialize
29
+ super 1, 4, -1
30
+ end
31
+
32
+ def call cpu
33
+ cpu.interrupt_before_halt = cpu.mmu.read_byte GBRb::INTERRUPT_FLAG_ADDR
34
+ cpu.halted = true
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ require_relative '../cpu'
2
+ require_relative 'instruction'
3
+
4
+ module GBRb::InstructionSet
5
+ class Pop < Instruction
6
+ def initialize target, m=3, t=12
7
+ super m, t
8
+ @targets = target.to_s.chars.map{|a| a.to_sym}.reverse
9
+ end
10
+
11
+ def call r, mem
12
+ @targets.each do |target|
13
+ r.public_send(target).store mem.read_byte(r.sp.read)
14
+ r.sp.store r.sp.read + 1
15
+ end
16
+
17
+ r.f.store r.f.read & 0xf0 if @targets.include? :f
18
+ end
19
+ end
20
+
21
+ class Push < Instruction
22
+ def initialize target, m=4, t=16
23
+ super m, t
24
+ @targets = target.to_s.chars.map{|a| a.to_sym}
25
+ end
26
+
27
+ def call r, mem
28
+ @targets.each do |target|
29
+ r.sp.store r.sp.read - 1
30
+ mem.write_byte(r.sp.read, r.public_send(target).read)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ require_relative '../cpu'
2
+ require_relative 'instruction'
3
+
4
+ module GBRb::InstructionSet
5
+ class Swap < Instruction
6
+ def initialize target, m=2, t=8, indirect=false
7
+ @target = target
8
+ @indirect = indirect
9
+ super m, t
10
+ end
11
+
12
+ def call r, mem
13
+ initial = r.public_send(@target).read
14
+ initial = mem.read_byte(initial) if @indirect
15
+ high = initial >> 4
16
+ low = initial & ((1 << 4) - 1)
17
+
18
+ result = (low << 4) + high
19
+
20
+ if @indirect
21
+ mem.write_byte r.public_send(@target).read, result
22
+ mem.read_byte(r.public_send(@target).read) == 0 ? r.set_zero_flag : r.clear_zero_flag
23
+ else
24
+ r.public_send(@target).store result
25
+ r.public_send(@target).read == 0 ? r.set_zero_flag : r.clear_zero_flag
26
+ end
27
+ r.clear_add_sub_flag
28
+ r.clear_half_carry_flag
29
+ r.clear_carry_flag
30
+ end
31
+ end
32
+ end
@@ -9,19 +9,23 @@ module GBRb
9
9
  WORKING_RAM_SHADOW_LENGTH = 0x1e00
10
10
  ROM_BANK_0_ADDRESS = 0x0000
11
11
  ROM_BANK_1_ADDRESS = 0x4000
12
- GPU_START = 0x8000
13
- GPU_END = 0x9fff
12
+ BIOS_STATUS_ADDRESS = 0xff50
14
13
 
15
- def initialize bios=[], cartridge=:none, gpu=nil
16
- @storage = Array.new SIZE, 0
17
- load_cartridge cartridge unless cartridge == :none
14
+ def initialize bios=[]
15
+ @storage = Array.new SIZE, 0
16
+ @peripherals = Array.new SIZE, nil
18
17
  load_bios bios
19
- @gpu = gpu
20
18
  end
21
19
 
22
20
  def read_byte addr
23
21
  raise unless (0..SIZE).cover? addr
24
- @storage[addr]
22
+ return @bios[addr] if addr < @bios.length and @in_bios
23
+
24
+ if (p = @peripherals[addr])
25
+ p.read_byte addr
26
+ else
27
+ @storage[addr]
28
+ end
25
29
  end
26
30
 
27
31
  def read_word addr
@@ -29,13 +33,19 @@ module GBRb
29
33
  end
30
34
 
31
35
  def write_byte addr, value
32
- @storage[addr] = value & 0xff
33
- if (WORKING_RAM_ADDRESS..WORKING_RAM_ADDRESS + WORKING_RAM_SHADOW_LENGTH).cover? addr
34
- copy_address = addr + WORKING_RAM_SHADOW_ADDRESS - WORKING_RAM_ADDRESS
35
- @storage[copy_address] = value & 0xff
36
+ if BIOS_STATUS_ADDRESS == addr
37
+ value == 0x00 ? load_bios(@bios) : unload_bios
38
+ return
36
39
  end
37
- if (GPU_START..GPU_END).cover? addr and @gpu
38
- @gpu.push [:mmu, addr]
40
+
41
+ if (p = @peripherals[addr])
42
+ p.write_byte addr, value
43
+ else
44
+ @storage[addr] = value & 0xff
45
+ if (WORKING_RAM_ADDRESS..WORKING_RAM_ADDRESS + WORKING_RAM_SHADOW_LENGTH).cover? addr
46
+ copy_address = addr + WORKING_RAM_SHADOW_ADDRESS - WORKING_RAM_ADDRESS
47
+ @storage[copy_address] = value & 0xff
48
+ end
39
49
  end
40
50
  end
41
51
 
@@ -44,12 +54,49 @@ module GBRb
44
54
  write_byte addr+1, value >> 8
45
55
  end
46
56
 
57
+ def connect peripheral, *locations
58
+ locations.map! do |location|
59
+ if location.class == Range
60
+ STDOUT.puts "Connecting #{peripheral.class.to_s.split('::').last} at [#{location.first.to_s 16}, #{location.last.to_s 16}]..."
61
+ location.to_a
62
+ else
63
+ STDOUT.puts "Connecting #{peripheral.class.to_s.split('::').last} at #{Array(location).map{|l| l.to_s 16}.join ", "}..."
64
+ Array(location).map &:to_i
65
+ end
66
+ end
67
+
68
+ locations.flatten.each do |l|
69
+ @peripherals[l.to_i] = peripheral
70
+ end
71
+ end
72
+
47
73
  def dump start=0, stop=SIZE
48
74
  debug_indicies(start, stop).zip(debug_hex(start, stop), debug_ascii(start, stop))
49
75
  .map{|row| row.join(" ")}
50
76
  .join("\n")
51
77
  end
52
78
 
79
+ def reset
80
+ end
81
+
82
+ def unload_bios
83
+ @in_bios = false
84
+ end
85
+
86
+ def irq i
87
+ old_value = read_byte INTERRUPT_FLAG_ADDR
88
+ case i
89
+ when TIMER_OVERFLOW_IRQ
90
+ write_byte INTERRUPT_FLAG_ADDR, old_value | TIMER_OVERFLOW_IRQ
91
+ end
92
+ end
93
+
94
+ private
95
+ def load_bios commands
96
+ @bios = commands
97
+ @in_bios = commands.length > 0
98
+ end
99
+
53
100
  def debug_indicies start, stop
54
101
  (start...stop).step(0x10)
55
102
  .map{|el| el.to_s 16 }
@@ -70,27 +117,5 @@ module GBRb
70
117
  .map{|el| "|" + el + "|"}
71
118
  end
72
119
 
73
- def reset
74
- end
75
-
76
- def unload_bios
77
- @storage[0..(ROM_BANK_0_ADDRESS + MEMORY_BANK_SIZE)] = if @cartridge
78
- @cartridge.bank
79
- else
80
- Array.new(ROM_BANK_0_ADDRESS + MEMORY_BANK_SIZE){0}
81
- end
82
- end
83
-
84
- private
85
- def load_cartridge cartridge
86
- @cartridge = cartridge
87
- @storage[0...(ROM_BANK_0_ADDRESS + MEMORY_BANK_SIZE)] = cartridge.bank 0
88
- @storage[ROM_BANK_1_ADDRESS...(ROM_BANK_1_ADDRESS + MEMORY_BANK_SIZE)] = cartridge.bank 1
89
- end
90
-
91
- def load_bios commands
92
- @bios = commands
93
- @storage[0..commands.length-1] = commands
94
- end
95
120
  end
96
121
  end
@@ -0,0 +1,54 @@
1
+ require 'optparse'
2
+
3
+ module GBRb
4
+ class Options
5
+ def initialize argv
6
+ if argv.length < 1
7
+ STDERR.puts "No game cartridge specified. See `gbrb -h` for usage."
8
+ exit 1
9
+ end
10
+
11
+ @config = { skip_boot: false,
12
+ debug: false,
13
+ cartridge_path: argv.pop
14
+ }
15
+
16
+ if @config[:cartridge_path] =~ /^-/
17
+ argv.push @config[:cartridge_path]
18
+ @config[:cartridge_path] = ''
19
+ end
20
+
21
+ parse argv
22
+ end
23
+
24
+ def method_missing m, *a, &b
25
+ @config.fetch(m) { super }
26
+ end
27
+
28
+ private
29
+ def parse argv
30
+ OptionParser.new do |opts|
31
+ opts.on("-d", "--debug", "Enable interactive debugger") do
32
+ @config[:debug] = :interactive
33
+ end
34
+ opts.on("-D", "--dump", "Dump the state of the registers after each instruction.") do
35
+ @config[:debug] = :dump
36
+ end
37
+ opts.on("-h", "--help", "Show this help screen") do
38
+ puts opts
39
+ exit
40
+ end
41
+ opts.on("-s", "--skip-boot", "Skip the Nintendo logo scroll") do
42
+ @config[:skip_boot] = true
43
+ end
44
+
45
+ begin
46
+ opts.parse! argv
47
+ rescue OptionParser::ParseError => e
48
+ STDERR.puts e.message, "\n", opts
49
+ exit 2
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,114 @@
1
+ require_relative '../gbrb'
2
+
3
+ module GBRb
4
+ class Timer
5
+ DIV_ADDRESS = 0xff04
6
+ TIMA_ADDRESS = 0xff05
7
+ TMA_ADDRESS = 0xff06
8
+ TAC_ADDRESS = 0xff07
9
+ ADDRESSES = [DIV_ADDRESS, TIMA_ADDRESS, TMA_ADDRESS, TAC_ADDRESS]
10
+
11
+ Frequency = Struct.new :string, :cycles
12
+ FREQUENCIES = { freq4096: Frequency.new('4096 Hz', 256),
13
+ freq16384: Frequency.new('16384 Hz', 64),
14
+ freq65536: Frequency.new('65536 Hz', 16),
15
+ freq262144: Frequency.new('262144 Hz', 4)
16
+ }
17
+
18
+ attr_reader :div, :tima, :tma, :tac
19
+ attr_writer :irq_handler
20
+
21
+ def initialize
22
+ @div = Counter.new 'DIV', :freq16384
23
+ @tima = Counter.new 'TIMA', :freq4096
24
+ @tac = 0x00
25
+ @tma = 0x00
26
+ end
27
+
28
+ def step cycles
29
+ if @tac & 0x04 == 0x04 and @tima.step cycles
30
+ @irq_handler.irq TIMER_OVERFLOW_IRQ
31
+ @tima.value = @tma
32
+ end
33
+ @div.step cycles
34
+ end
35
+
36
+ def read_byte addr
37
+ case addr
38
+ when DIV_ADDRESS
39
+ @div.value
40
+ when TIMA_ADDRESS
41
+ @tima.value
42
+ when TMA_ADDRESS
43
+ @tma
44
+ when TAC_ADDRESS
45
+ @tac
46
+ end
47
+ end
48
+
49
+ def write_byte addr, value
50
+ case addr
51
+ when DIV_ADDRESS
52
+ @div.value = 0
53
+ when TIMA_ADDRESS
54
+ @tima.value = value
55
+ when TMA_ADDRESS
56
+ @tma = value
57
+ when TAC_ADDRESS
58
+ old_freq = get_frequency(@tac & 0x03)
59
+ new_freq = get_frequency(value & 0x03)
60
+ @tima.set_frequency new_freq unless old_freq == new_freq
61
+ @tac = value
62
+ end
63
+ end
64
+
65
+ def get_frequency id
66
+ case id
67
+ when 0
68
+ :freq4096
69
+ when 1
70
+ :freq262144
71
+ when 2
72
+ :freq65536
73
+ when 3
74
+ :freq16384
75
+ end
76
+ end
77
+
78
+ private
79
+ class Counter
80
+ attr_accessor :value
81
+
82
+ def initialize name, initial_frequency
83
+ reset
84
+ @name = name
85
+ set_frequency initial_frequency
86
+ end
87
+
88
+ def reset
89
+ @frequency = FREQUENCIES[:freq4096]
90
+ @value = 0
91
+ end
92
+
93
+ def set_frequency frequency
94
+ @frequency = FREQUENCIES[frequency] if FREQUENCIES.has_key? frequency
95
+ @clock_counter = @frequency.cycles
96
+ end
97
+
98
+ def to_s
99
+ "#{@name}: Frequency: #{@frequency.string} (#{@frequency.cycles} cycles) | Current Counter: #{@clock_counter} | Value: #{@value}"
100
+ end
101
+
102
+ def step cycles
103
+ @clock_counter -= cycles
104
+
105
+ while @clock_counter <= 0
106
+ @value = (@value + 1) & 0xff
107
+ @clock_counter += @frequency.cycles
108
+ return true if @value == 0
109
+ end
110
+ false
111
+ end
112
+ end
113
+ end
114
+ end
@@ -1,3 +1,3 @@
1
1
  module GBRb
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ REGISTERS = %i[pc sp a b c d e h l f opcode]
4
+
5
+ class Cpu
6
+ attr_accessor :registers
7
+
8
+ def initialize values=nil
9
+ if values
10
+ @registers = values
11
+ else
12
+ @registers = {}
13
+ REGISTERS.each {|r| @registers[r.to_sym] = 0}
14
+ end
15
+ end
16
+
17
+ def to_s
18
+ @registers.inspect
19
+ end
20
+
21
+ def == other
22
+ @registers == other.registers
23
+ end
24
+ end
25
+
26
+ class Parser
27
+ include Enumerable
28
+
29
+ def initialize input, separator="\n"
30
+ @input = input
31
+ @separator = separator
32
+ skip_preamble
33
+ end
34
+
35
+ def skip_preamble
36
+ raise StandardError
37
+ end
38
+
39
+ def succ
40
+ raise StopIteration if @input.eof?
41
+ @current_raw_value = @input.gets(@separator).chomp(@separator)
42
+ end
43
+ alias :next :succ
44
+
45
+ def rewind
46
+ @input.rewind
47
+ end
48
+
49
+ def destroy
50
+ @input.close
51
+ end
52
+
53
+ def each &block
54
+ return self.dup unless block
55
+ loop { block.call succ }
56
+ end
57
+ end
58
+
59
+ class GBRbParser < Parser
60
+ def current
61
+ Cpu.new Hash[
62
+ REGISTERS.zip( @current_raw_value.split('|')
63
+ .tap{|el| el.delete_at(10)} # Remove CB prefix
64
+ .map{|el| el.to_i(16)}
65
+ )
66
+ ]
67
+ end
68
+
69
+ def skip_preamble
70
+ separator_count = 0
71
+ loop do
72
+ c = @input.getc
73
+ if /\*/.match c
74
+ separator_count += 1
75
+ c = @input.getc until c == "\n"
76
+ end
77
+ if separator_count == 2
78
+ self.next
79
+ break
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ class GomeboyParser < Parser
86
+ def current
87
+ tmp = @current_raw_value.split(' ')
88
+ .map{|el| el.split(': ') }
89
+ .take(11)
90
+ .map{|el| el.length == 2 ? el : el.first.split(' ').first}
91
+
92
+ tmp[-1] = ["OPCODE", tmp[-1]]
93
+ tmp[-2] = ["F", "0x" + tmp[-2].chars.each_with_index.map{|el, i| el == '-' ? 0 : 0b1 << (7-i)}.inject(0, &:+).to_s(16)]
94
+ Cpu.new Hash[ tmp.map{|el| [el.first.downcase.to_sym, el.last.to_i(16)]} ]
95
+ end
96
+
97
+ def skip_preamble
98
+ self.next
99
+ separator_count = 0
100
+ loop do
101
+ c = @input.getc
102
+ if /\-/.match c
103
+ separator_count += 1
104
+ c = @input.getc until c == "\n"
105
+ end
106
+ break if separator_count == 2
107
+ end
108
+ self.next
109
+ end
110
+ end
111
+
112
+ if ARGV.length != 2
113
+ puts "Two dumps required"
114
+ exit 1
115
+ end
116
+
117
+ gbrb = GBRbParser.new File.open ARGV[0]
118
+ gomeboy = GomeboyParser.new File.open ARGV[1]
119
+
120
+ steps_matched = 0
121
+ different = false
122
+ while !different do
123
+ gomeboy.next
124
+ gbrb.next
125
+ if gomeboy.current != gbrb.current
126
+ puts "Gomeboy: #{gomeboy.current}"
127
+ puts "GBRb: #{gbrb.current}"
128
+ different = true
129
+ end
130
+ steps_matched += 1
131
+ end
132
+
133
+ puts "Steps Matched: #{steps_matched}"