rubyboy 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.
Binary file
@@ -0,0 +1,119 @@
1
+ Game Boy CPU Instruction Behavior Test
2
+ --------------------------------------
3
+ This ROM tests the behavior of all CPU instructions except STOP and the
4
+ 11 illegal opcodes. The tests are fairly thorough, running instructions
5
+ with boundary data and verifying both the result and that other
6
+ registers are not modified. Instructions which perform the same
7
+ operation on different registers are each tested just as thoroughly, in
8
+ case an emulator implements each independently. Some sub-tests take half
9
+ minute to complete.
10
+
11
+ Failed instructions are listed as
12
+
13
+ [CB] opcode
14
+
15
+ Some errors cannot of course be diagnosed properly, since the test
16
+ framework itself relies on basic instruction behavior being correct.
17
+
18
+
19
+ Internal operation
20
+ ------------------
21
+ The main tests use a framework that runs each instruction in a loop,
22
+ varying the register values on input and examining them on output.
23
+ Rather than keep a table of correct values, it simply calculates a
24
+ CRC-32 checksum of all the output, then compares this with the correct
25
+ value. Instructions are divided into several groups, each with a
26
+ different set of input values suited for their behavior; for example,
27
+ the bit test instructions are fed $01, $02, $04 ... $40, $80, to ensure
28
+ each bit is handled properly, while the arithmetic instructions are fed
29
+ $01, $0F, $10, $7F, $FF, to exercise carry and half-carry. A few
30
+ instructions require a custom test due to their uniqueness.
31
+
32
+
33
+ Multi-ROM
34
+ ---------
35
+ In the main directory is a single ROM which runs all the tests. It
36
+ prints a test's number, runs the test, then "ok" if it passes, otherwise
37
+ a failure code. Once all tests have completed it either reports that all
38
+ tests passed, or prints the number of failed tests. Finally, it makes
39
+ several beeps. If a test fails, it can be run on its own by finding the
40
+ corresponding ROM in individual/.
41
+
42
+ Ths compact format on screen is to avoid having the results scroll off
43
+ the top, so the test can be started and allowed to run without having to
44
+ constantly monitor the display.
45
+
46
+ Currently there is no well-defined way for an emulator test rig to
47
+ programatically find the result of the test; contact me if you're trying
48
+ to do completely automated testing of your emulator. One simple approach
49
+ is to take a screenshot after all tests have run, or even just a
50
+ checksum of one, and compare this with a previous run.
51
+
52
+
53
+ Failure codes
54
+ -------------
55
+ Failed tests may print a failure code, and also short description of the
56
+ problem. For more information about a failure code, look in the
57
+ corresponding source file in source/; the point in the code where
58
+ "set_test n" occurs is where that failure code will be generated.
59
+ Failure code 1 is a general failure of the test; any further information
60
+ will be printed.
61
+
62
+ Note that once a sub-test fails, no further tests for that file are run.
63
+
64
+
65
+ Console output
66
+ --------------
67
+ Information is printed on screen in a way that needs only minimum LCD
68
+ support, and won't hang if LCD output isn't supported at all.
69
+ Specifically, while polling LY to wait for vblank, it will time out if
70
+ it takes too long, so LY always reading back as the same value won't
71
+ hang the test. It's also OK if scrolling isn't supported; in this case,
72
+ text will appear starting at the top of the screen.
73
+
74
+ Everything printed on screen is also sent to the game link port by
75
+ writing the character to SB, then writing $81 to SC. This is useful for
76
+ tests which print lots of information that scrolls off screen.
77
+
78
+
79
+ Source code
80
+ -----------
81
+ Source code is included for all tests, in source/. It can be used to
82
+ build the individual test ROMs. Code for the multi test isn't included
83
+ due to the complexity of putting everything together.
84
+
85
+ Code is written for the wla-dx assembler. To assemble a particular test,
86
+ execute
87
+
88
+ wla -o "source_filename.s" test.o
89
+ wlalink linkfile test.gb
90
+
91
+ Test code uses a common shell framework contained in common/.
92
+
93
+
94
+ Internal framework operation
95
+ ----------------------------
96
+ Tests use a common framework for setting things up, reporting results,
97
+ and ending. All files first include "shell.inc", which sets up the ROM
98
+ header and shell code, and includes other commonly-used modules.
99
+
100
+ One oddity is that test code is first copied to internal RAM at $D000,
101
+ then executed there. This allows self-modification, and ensures the code
102
+ is executed the same way it is on my devcart, which doesn't have a
103
+ rewritable ROM as most do.
104
+
105
+ Some macros are used to simplify common tasks:
106
+
107
+ Macro Behavior
108
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
109
+ wreg addr,data Writes data to addr using LDH
110
+ lda addr Loads byte from addr into A using LDH
111
+ sta addr Stores A at addr using LDH
112
+ delay n Delays n cycles, where NOP = 1 cycle
113
+ delay_msec n Delays n milliseconds
114
+ set_test n,"Cause" Sets failure code and optional string
115
+
116
+ Routines and macros are documented where they are defined.
117
+
118
+ --
119
+ Shay Green <gblargg@gmail.com>
@@ -0,0 +1,139 @@
1
+ Game Boy CPU Instruction Timing Test
2
+ ------------------------------------
3
+ This ROM tests the timings of all CPU instructions except HALT, STOP,
4
+ and the 11 illegal opcodes. For conditional instructions, it tests taken
5
+ and not taken timings. This test requires proper timer operation (TAC,
6
+ TIMA, TMA).
7
+
8
+ Failed instructions are listed as
9
+
10
+ [CB] opcode:measured time-correct time
11
+
12
+ Times are in terms of instruction cycles, where NOP takes one cycle.
13
+
14
+
15
+ Verified cycle timing tables
16
+ ----------------------------
17
+ The test internally uses a table of proper cycle times, which can be
18
+ used in an emulator to ensure proper timing. The only changes below are
19
+ removal of the .byte prefixes, and addition of commas at the ends, so
20
+ that they can be used without changes in most programming languages. For
21
+ added correctness assurance, the original tables can be found at the end
22
+ of the source code.
23
+
24
+ Normal instructions:
25
+
26
+ 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1,
27
+ 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1,
28
+ 2,3,2,2,1,1,2,1,2,2,2,2,1,1,2,1,
29
+ 2,3,2,2,3,3,3,1,2,2,2,2,1,1,2,1,
30
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
31
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
32
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
33
+ 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1,
34
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
35
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
36
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
37
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
38
+ 2,3,3,4,3,4,2,4,2,4,3,0,3,6,2,4,
39
+ 2,3,3,0,3,4,2,4,2,4,3,0,3,0,2,4,
40
+ 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4,
41
+ 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4
42
+
43
+ CB-prefixed instructions:
44
+
45
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
46
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
47
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
48
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
49
+ 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2,
50
+ 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2,
51
+ 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2,
52
+ 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2,
53
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
54
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
55
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
56
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
57
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
58
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
59
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
60
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
61
+
62
+
63
+ Internal operation
64
+ ------------------
65
+ Before each instruction is executed, the test sets up registers and
66
+ memory in such a way that the instruction will cleanly execute and then
67
+ end up at a common destination, without trashing anything important. The
68
+ timing itself is done by first synchronizing to the timer via a loop,
69
+ executing the instruction, then using a similar loop to determine how
70
+ many clocks elapsed.
71
+
72
+
73
+ Failure codes
74
+ -------------
75
+ Failed tests may print a failure code, and also short description of the
76
+ problem. For more information about a failure code, look in the
77
+ corresponding source file in source/; the point in the code where
78
+ "set_test n" occurs is where that failure code will be generated.
79
+ Failure code 1 is a general failure of the test; any further information
80
+ will be printed.
81
+
82
+ Note that once a sub-test fails, no further tests for that file are run.
83
+
84
+
85
+ Console output
86
+ --------------
87
+ Information is printed on screen in a way that needs only minimum LCD
88
+ support, and won't hang if LCD output isn't supported at all.
89
+ Specifically, while polling LY to wait for vblank, it will time out if
90
+ it takes too long, so LY always reading back as the same value won't
91
+ hang the test. It's also OK if scrolling isn't supported; in this case,
92
+ text will appear starting at the top of the screen.
93
+
94
+ Everything printed on screen is also sent to the game link port by
95
+ writing the character to SB, then writing $81 to SC. This is useful for
96
+ tests which print lots of information that scrolls off screen.
97
+
98
+
99
+ Source code
100
+ -----------
101
+ Source code is included for all tests, in source/. It can be used to
102
+ build the individual test ROMs. Code for the multi test isn't included
103
+ due to the complexity of putting everything together.
104
+
105
+ Code is written for the wla-dx assembler. To assemble a particular test,
106
+ execute
107
+
108
+ wla -o "source_filename.s" test.o
109
+ wlalink linkfile test.gb
110
+
111
+ Test code uses a common shell framework contained in common/.
112
+
113
+
114
+ Internal framework operation
115
+ ----------------------------
116
+ Tests use a common framework for setting things up, reporting results,
117
+ and ending. All files first include "shell.inc", which sets up the ROM
118
+ header and shell code, and includes other commonly-used modules.
119
+
120
+ One oddity is that test code is first copied to internal RAM at $D000,
121
+ then executed there. This allows self-modification, and ensures the code
122
+ is executed the same way it is on my devcart, which doesn't have a
123
+ rewritable ROM as most do.
124
+
125
+ Some macros are used to simplify common tasks:
126
+
127
+ Macro Behavior
128
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
129
+ wreg addr,data Writes data to addr using LDH
130
+ lda addr Loads byte from addr into A using LDH
131
+ sta addr Stores A at addr using LDH
132
+ delay n Delays n cycles, where NOP = 1 cycle
133
+ delay_msec n Delays n milliseconds
134
+ set_test n,"Cause" Sets failure code and optional string
135
+
136
+ Routines and macros are documented where they are defined.
137
+
138
+ --
139
+ Shay Green <gblargg@gmail.com>
data/lib/rubyboy/bus.rb CHANGED
@@ -1,62 +1,201 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'cartridge/factory'
4
+ require_relative 'interrupt'
5
+ require_relative 'timer'
6
+
3
7
  module Rubyboy
4
8
  class Bus
5
- attr_accessor :ppu, :rom
9
+ attr_accessor :ppu, :rom, :interrupt
6
10
 
7
- def initialize(ppu, rom)
11
+ def initialize(ppu, rom, timer, interrupt)
8
12
  @ppu = ppu
9
13
  @rom = rom
14
+ @ram = Ram.new
15
+ @mbc = Cartridge::Factory.create(rom, @ram)
16
+
17
+ @interrupt = interrupt
18
+ @timer = timer
19
+
20
+ @oam = Array.new(0xa0, 0)
21
+
22
+ @tmp = {}
10
23
  end
11
24
 
12
25
  def read_byte(addr)
13
26
  case addr
14
27
  when 0x0000..0x7fff
15
- @rom.data[addr]
28
+ @mbc.read_byte(addr)
16
29
  when 0x8000..0x9fff
17
30
  @ppu.vram[addr - 0x8000]
18
- when 0xff26
19
- # nr52
31
+ when 0xa000..0xdfff
32
+ @mbc.read_byte(addr)
33
+ when 0xe000..0xfdff
34
+ # echo ram
35
+ when 0xfe00..0xfe9f
36
+ @oam[addr - 0xfe00]
37
+ when 0xfea0..0xfeff
38
+ # unused
39
+ 0xff
40
+ when 0xff00
41
+ # joypad
42
+ @tmp[addr] ||= 0
43
+ when 0xff01..0xff02
44
+ # serial
45
+ @tmp[addr] ||= 0
46
+ when 0xff04..0xff07
47
+ @timer.read_byte(addr)
48
+ when 0xff0f
49
+ @interrupt.if
50
+ when 0xff10..0xff26
51
+ # sound
52
+ @tmp[addr] ||= 0
53
+ when 0xff30..0xff3f
54
+ # wave pattern ram
55
+ @tmp[addr] ||= 0
20
56
  when 0xff40
21
57
  @ppu.lcdc
58
+ when 0xff41
59
+ # stat
60
+ @tmp[addr] ||= 0
22
61
  when 0xff42
23
62
  @ppu.scy
24
63
  when 0xff43
25
64
  @ppu.scx
26
65
  when 0xff44
27
66
  @ppu.ly
67
+ when 0xff45
68
+ @ppu.lyc
69
+ when 0xff46
70
+ # dma
71
+ @tmp[addr] ||= 0
28
72
  when 0xff47
29
73
  @ppu.bgp
74
+ when 0xff48
75
+ # obp0
76
+ @tmp[addr] ||= 0
77
+ when 0xff49
78
+ # obp1
79
+ @tmp[addr] ||= 0
80
+ when 0xff4a
81
+ # wy
82
+ @tmp[addr] ||= 0
83
+ when 0xff4b
84
+ # wx
85
+ @tmp[addr] ||= 0
86
+ when 0xff4f
87
+ # vbk
88
+ @tmp[addr] ||= 0
89
+ when 0xff50
90
+ # boot rom
91
+ @tmp[addr] ||= 0
92
+ when 0xff51..0xff55
93
+ # hdma
94
+ @tmp[addr] ||= 0
95
+ when 0xff68..0xff6b
96
+ # bgp
97
+ @tmp[addr] ||= 0
98
+ when 0xff70
99
+ # svbk
100
+ @tmp[addr] ||= 0
101
+ when 0xff80..0xfffe
102
+ @ram.hram[addr - 0xff80]
103
+ when 0xffff
104
+ @interrupt.ie
30
105
  else
31
- raise "not implemented: write_byte #{addr}"
106
+ 0xff
32
107
  end
33
108
  end
34
109
 
35
110
  def write_byte(addr, value)
36
111
  case addr
37
112
  when 0x0000..0x7fff
38
- @rom.data[addr] = value
113
+ @mbc.write_byte(addr, value)
39
114
  when 0x8000..0x9fff
40
115
  @ppu.vram[addr - 0x8000] = value
41
- when 0xff26
42
- # nr52
116
+ when 0xa000..0xdfff
117
+ @mbc.write_byte(addr, value)
118
+ when 0xe000..0xfdff
119
+ # echo ram
120
+ when 0xfe00..0xfe9f
121
+ @oam[addr - 0xfe00] = value
122
+ when 0xfea0..0xfeff
123
+ # unused
124
+ when 0xff00
125
+ # joypad
126
+ @tmp[addr] = value
127
+ when 0xff01..0xff02
128
+ # serial
129
+ @tmp[addr] = value
130
+ when 0xff04..0xff07
131
+ @timer.write_byte(addr, value)
132
+ when 0xff0f
133
+ @interrupt.if = value
134
+ when 0xff10..0xff26
135
+ # sound
136
+ @tmp[addr] = value
137
+ when 0xff30..0xff3f
138
+ # wave pattern ram
139
+ @tmp[addr] = value
43
140
  when 0xff40
44
141
  @ppu.lcdc = value
142
+ when 0xff41
143
+ # stat
144
+ @tmp[addr] = value
45
145
  when 0xff42
46
146
  @ppu.scy = value
47
147
  when 0xff43
48
148
  @ppu.scx = value
49
149
  when 0xff44
50
150
  @ppu.ly = value
151
+ when 0xff45
152
+ @ppu.lyc = value
153
+ when 0xff46
154
+ # dma
155
+ @tmp[addr] = value
51
156
  when 0xff47
52
157
  @ppu.bgp = value
53
- else
54
- raise "not implemented: write_byte #{addr}"
158
+ when 0xff48
159
+ # obp0
160
+ @tmp[addr] = value
161
+ when 0xff49
162
+ # obp1
163
+ @tmp[addr] = value
164
+ when 0xff4a
165
+ # wy
166
+ @tmp[addr] = value
167
+ when 0xff4b
168
+ # wx
169
+ @tmp[addr] = value
170
+ when 0xff4f
171
+ # vbk
172
+ @tmp[addr] = value
173
+ when 0xff50
174
+ # boot rom
175
+ @tmp[addr] = value
176
+ when 0xff51..0xff55
177
+ # hdma
178
+ @tmp[addr] = value
179
+ when 0xff68..0xff6b
180
+ # bgp
181
+ @tmp[addr] = value
182
+ when 0xff70
183
+ # svbk
184
+ @tmp[addr] = value
185
+ when 0xff80..0xfffe
186
+ @ram.hram[addr - 0xff80] = value
187
+ when 0xffff
188
+ @interrupt.ie = value
55
189
  end
56
190
  end
57
191
 
58
192
  def read_word(addr)
59
193
  read_byte(addr) + (read_byte(addr + 1) << 8)
60
194
  end
195
+
196
+ def write_word(addr, value)
197
+ write_byte(addr, value & 0xff)
198
+ write_byte(addr + 1, value >> 8)
199
+ end
61
200
  end
62
201
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'nombc'
4
+ require_relative 'mbc1'
5
+
6
+ module Rubyboy
7
+ module Cartridge
8
+ class Factory
9
+ def self.create(rom, ram)
10
+ case rom.cartridge_type
11
+ when 0x00
12
+ Nombc.new(rom)
13
+ when 0x01..0x03
14
+ Mbc1.new(rom, ram)
15
+ else
16
+ raise "Unsupported cartridge type: #{cartridge_type}"
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../ram'
4
+
5
+ module Rubyboy
6
+ module Cartridge
7
+ class Mbc1
8
+ def initialize(rom, ram)
9
+ @rom = rom
10
+ @ram = ram
11
+ @rom_bank = 1
12
+ @ram_bank = 0
13
+ @ram_enable = false
14
+ @ram_banking_mode = false
15
+ end
16
+
17
+ def read_byte(addr)
18
+ case addr
19
+ when 0x0000..0x3fff
20
+ @rom.data[addr]
21
+ when 0x4000..0x7fff
22
+ @rom.data[addr + (@rom_bank - 1) * 0x4000]
23
+ when 0xa000..0xbfff
24
+ if @ram_enable
25
+ if @ram_banking_mode
26
+ @ram.eram[addr - 0xa000 + @ram_bank * 0x800]
27
+ else
28
+ @ram.eram[addr - 0xa000]
29
+ end
30
+ else
31
+ 0xff
32
+ end
33
+ when 0xc000..0xcfff
34
+ @ram.wram1[addr - 0xc000]
35
+ when 0xd000..0xdfff
36
+ @ram.wram2[addr - 0xd000]
37
+ else
38
+ raise "not implemented: read_byte #{addr}"
39
+ end
40
+ end
41
+
42
+ def write_byte(addr, value)
43
+ case addr
44
+ when 0x0000..0x1fff
45
+ @ram_enable = value & 0x0f == 0x0a
46
+ @rom.data[addr] = value
47
+ when 0x2000..0x3fff
48
+ @rom_bank = value & 0x1f
49
+ @rom_bank = 1 if @rom_bank.zero?
50
+ @rom.data[addr] = value
51
+ when 0x4000..0x5fff
52
+ @ram_bank = value & 0x03
53
+ when 0x6000..0x7fff
54
+ @ram_banking_mode = value & 0x01 == 0x01
55
+ when 0xa000..0xbfff
56
+ if @ram_enable
57
+ if @ram_banking_mode
58
+ @ram.eram[addr - 0xa000 + @ram_bank * 0x800] = value
59
+ else
60
+ @ram.eram[addr - 0xa000] = value
61
+ end
62
+ end
63
+ when 0xc000..0xcfff
64
+ @ram.wram1[addr - 0xc000] = value
65
+ when 0xd000..0xdfff
66
+ @ram.wram2[addr - 0xd000] = value
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ module Cartridge
5
+ class Nombc
6
+ def initialize(rom)
7
+ @rom = rom
8
+ end
9
+
10
+ def read_byte(addr)
11
+ @rom.data[addr]
12
+ end
13
+
14
+ def write_byte(_addr, _value)
15
+ # do nothing
16
+ end
17
+ end
18
+ end
19
+ end