GBRb 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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}"