Class: Amaterasu::GameBoy::Cpu
- Inherits:
-
Object
- Object
- Amaterasu::GameBoy::Cpu
- Defined in:
- lib/amaterasu/game_boy/cpu.rb,
lib/amaterasu/game_boy/cpu/registers.rb,
lib/amaterasu/game_boy/cpu/instructions.rb,
lib/amaterasu/game_boy/cpu/instructions/cp.rb,
lib/amaterasu/game_boy/cpu/instructions/di.rb,
lib/amaterasu/game_boy/cpu/instructions/ei.rb,
lib/amaterasu/game_boy/cpu/instructions/jp.rb,
lib/amaterasu/game_boy/cpu/instructions/jr.rb,
lib/amaterasu/game_boy/cpu/instructions/or.rb,
lib/amaterasu/game_boy/cpu/instructions/adc.rb,
lib/amaterasu/game_boy/cpu/instructions/and.rb,
lib/amaterasu/game_boy/cpu/instructions/daa.rb,
lib/amaterasu/game_boy/cpu/instructions/dec.rb,
lib/amaterasu/game_boy/cpu/instructions/inc.rb,
lib/amaterasu/game_boy/cpu/instructions/ld8.rb,
lib/amaterasu/game_boy/cpu/instructions/ldh.rb,
lib/amaterasu/game_boy/cpu/instructions/nop.rb,
lib/amaterasu/game_boy/cpu/instructions/pop.rb,
lib/amaterasu/game_boy/cpu/instructions/ret.rb,
lib/amaterasu/game_boy/cpu/instructions/rst.rb,
lib/amaterasu/game_boy/cpu/instructions/sbc.rb,
lib/amaterasu/game_boy/cpu/instructions/sub.rb,
lib/amaterasu/game_boy/cpu/instructions/xor.rb,
lib/amaterasu/game_boy/cpu/instructions/add8.rb,
lib/amaterasu/game_boy/cpu/instructions/base.rb,
lib/amaterasu/game_boy/cpu/instructions/call.rb,
lib/amaterasu/game_boy/cpu/instructions/halt.rb,
lib/amaterasu/game_boy/cpu/instructions/ld16.rb,
lib/amaterasu/game_boy/cpu/instructions/misc.rb,
lib/amaterasu/game_boy/cpu/instructions/push.rb,
lib/amaterasu/game_boy/cpu/instructions/stop.rb,
lib/amaterasu/game_boy/cpu/instructions/add16.rb,
lib/amaterasu/game_boy/cpu/instructions/cb_rl.rb,
lib/amaterasu/game_boy/cpu/instructions/cb_rr.rb,
lib/amaterasu/game_boy/cpu/instructions/cb_bit.rb,
lib/amaterasu/game_boy/cpu/instructions/cb_res.rb,
lib/amaterasu/game_boy/cpu/instructions/cb_rlc.rb,
lib/amaterasu/game_boy/cpu/instructions/cb_rrc.rb,
lib/amaterasu/game_boy/cpu/instructions/cb_set.rb,
lib/amaterasu/game_boy/cpu/instructions/cb_sla.rb,
lib/amaterasu/game_boy/cpu/instructions/cb_sra.rb,
lib/amaterasu/game_boy/cpu/instructions/cb_srl.rb,
lib/amaterasu/game_boy/cpu/instructions/rotate.rb,
lib/amaterasu/game_boy/cpu/instructions/cb_swap.rb,
sig/akane/game_boy/cpu.rbs,
sig/akane/game_boy/cpu/registers.rbs,
sig/akane/game_boy/cpu/instructions.rbs,
sig/akane/game_boy/cpu/instructions/cp.rbs,
sig/akane/game_boy/cpu/instructions/di.rbs,
sig/akane/game_boy/cpu/instructions/ei.rbs,
sig/akane/game_boy/cpu/instructions/jp.rbs,
sig/akane/game_boy/cpu/instructions/jr.rbs,
sig/akane/game_boy/cpu/instructions/or.rbs,
sig/akane/game_boy/cpu/instructions/adc.rbs,
sig/akane/game_boy/cpu/instructions/and.rbs,
sig/akane/game_boy/cpu/instructions/daa.rbs,
sig/akane/game_boy/cpu/instructions/dec.rbs,
sig/akane/game_boy/cpu/instructions/inc.rbs,
sig/akane/game_boy/cpu/instructions/ld8.rbs,
sig/akane/game_boy/cpu/instructions/ldh.rbs,
sig/akane/game_boy/cpu/instructions/nop.rbs,
sig/akane/game_boy/cpu/instructions/pop.rbs,
sig/akane/game_boy/cpu/instructions/ret.rbs,
sig/akane/game_boy/cpu/instructions/rst.rbs,
sig/akane/game_boy/cpu/instructions/sbc.rbs,
sig/akane/game_boy/cpu/instructions/sub.rbs,
sig/akane/game_boy/cpu/instructions/xor.rbs,
sig/akane/game_boy/cpu/instructions/add8.rbs,
sig/akane/game_boy/cpu/instructions/base.rbs,
sig/akane/game_boy/cpu/instructions/call.rbs,
sig/akane/game_boy/cpu/instructions/halt.rbs,
sig/akane/game_boy/cpu/instructions/ld16.rbs,
sig/akane/game_boy/cpu/instructions/misc.rbs,
sig/akane/game_boy/cpu/instructions/push.rbs,
sig/akane/game_boy/cpu/instructions/stop.rbs,
sig/akane/game_boy/cpu/instructions/add16.rbs,
sig/akane/game_boy/cpu/instructions/cb_rl.rbs,
sig/akane/game_boy/cpu/instructions/cb_rr.rbs,
sig/akane/game_boy/cpu/instructions/cb_bit.rbs,
sig/akane/game_boy/cpu/instructions/cb_res.rbs,
sig/akane/game_boy/cpu/instructions/cb_rlc.rbs,
sig/akane/game_boy/cpu/instructions/cb_rrc.rbs,
sig/akane/game_boy/cpu/instructions/cb_set.rbs,
sig/akane/game_boy/cpu/instructions/cb_sla.rbs,
sig/akane/game_boy/cpu/instructions/cb_sra.rbs,
sig/akane/game_boy/cpu/instructions/cb_srl.rbs,
sig/akane/game_boy/cpu/instructions/rotate.rbs,
sig/akane/game_boy/cpu/instructions/cb_swap.rbs
Overview
Models the CPU behavior from the Game Boy.
Defined Under Namespace
Modules: Instructions Classes: Registers
Constant Summary collapse
- INSTRUCTIONS =
- CB_INSTRUCTIONS =
Instance Attribute Summary collapse
-
#m_cycles ⇒ Object
readonly
Returns the value of attribute m_cycles.
-
#registers ⇒ Registers
readonly
Returns the value of attribute registers.
Instance Method Summary collapse
-
#add16(value1, value2) ⇒ Integer
Performs an addition envolving a 16-bit value, CPU consumes an additional cycle to handle 16-bit values.
-
#bus_read(address:) ⇒ Integer
Reads a byte from the Bus at a given address.
-
#bus_write(address:, value:) ⇒ void
Requests a Bus write at a given address with a given value.
-
#decode_instruction ⇒ void
Determines which instruction should be executed for each Opcode.
-
#disable_interrupts ⇒ void
Used by the DI instruction.
-
#enable_interrupts ⇒ void
Used by the EI instruction.
-
#execute_instruction ⇒ void
Executes the logic for the current instruction.
-
#fetch_next_byte ⇒ Integer
Fetches the next immediate byte from memory pointed to by the Program Counter.
-
#fetch_next_word ⇒ Integer
Fetches the next 2 immediate bytes from memory.
- #halt ⇒ void
-
#handle_interrupts ⇒ void
Is only called if IME and any interrupt is pending.
-
#initialize(bus, hram, interrupts, advance_cycle, trace_cpu: false) ⇒ Cpu
constructor
A new instance of Cpu.
-
#internal_processing ⇒ void
Emulates CPU internal processing which advance cycles without Bus access.
-
#jump_to(address:) ⇒ void
Jumps execution to a given address by setting the address value into the PC.
- #log_state(old_pc, old_cycles, instruction) ⇒ void
-
#sign_value(byte) ⇒ Integer
Converts an unsigned byte into a value between -128 to 127 to use as an offset.
-
#stack_pop ⇒ Integer
Pops a 16-bit value from the Stack.
-
#stack_push(value:) ⇒ void
Pushes a 16-bit value into the Stack.
-
#step ⇒ void
Core CPU loop:.
-
#sub16(value1, value2) ⇒ Integer
Performs a subtraction envolving a 16-bit value, CPU consumes an additional cycle to handle 16-bit values.
Constructor Details
#initialize(bus, hram, interrupts, advance_cycle, trace_cpu: false) ⇒ Cpu
Returns a new instance of Cpu.
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 9 def initialize(bus, hram, interrupts, advance_cycle, trace_cpu: false) @bus = bus @hram = hram @interrupts = interrupts @advance_cycle = advance_cycle @trace_cpu = trace_cpu @registers = Registers.new @ime = false @ime_scheduled = false @halted = false @opcode = nil @instruction = nil @m_cycles = 0 @instructions = Instructions.load_base_instructions(cpu: self) @cb_instructions = Instructions.load_cb_instructions(cpu: self) end |
Instance Attribute Details
#m_cycles ⇒ Object (readonly)
Returns the value of attribute m_cycles.
7 8 9 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 7 def m_cycles @m_cycles end |
#registers ⇒ Registers (readonly)
Returns the value of attribute registers.
7 8 9 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 7 def registers @registers end |
Instance Method Details
#add16(value1, value2) ⇒ Integer
Performs an addition envolving a 16-bit value, CPU consumes an additional cycle to handle 16-bit values.
135 136 137 138 139 140 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 135 def add16(value1, value2) result = value1 + value2 internal_processing result end |
#bus_read(address:) ⇒ Integer
Reads a byte from the Bus at a given address.
64 65 66 67 68 69 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 64 def bus_read(address:) byte = @bus.read_byte(address:, caller: self) @m_cycles = @advance_cycle.call byte end |
#bus_write(address:, value:) ⇒ void
This method returns an undefined value.
Requests a Bus write at a given address with a given value.
72 73 74 75 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 72 def bus_write(address:, value:) @bus.write_byte(address:, value:, caller: self) @m_cycles = @advance_cycle.call end |
#decode_instruction ⇒ void
This method returns an undefined value.
Determines which instruction should be executed for each Opcode.
185 186 187 188 189 190 191 192 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 185 def decode_instruction if @opcode == 0xCB @opcode = fetch_next_byte @instruction = @cb_instructions[@opcode] else @instruction = @instructions[@opcode] end end |
#disable_interrupts ⇒ void
This method returns an undefined value.
Used by the DI instruction.
152 153 154 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 152 def disable_interrupts @ime = false end |
#enable_interrupts ⇒ void
This method returns an undefined value.
Used by the EI instruction.
157 158 159 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 157 def enable_interrupts @ime_scheduled = true end |
#execute_instruction ⇒ void
This method returns an undefined value.
Executes the logic for the current instruction.
195 196 197 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 195 def execute_instruction @instruction.execute end |
#fetch_next_byte ⇒ Integer
Fetches the next immediate byte from memory pointed to by the Program Counter. Every time a byte is fetched, the PC is incremented by 1.
79 80 81 82 83 84 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 79 def fetch_next_byte byte = bus_read(address: @registers.pc) @registers.pc += 1 byte end |
#fetch_next_word ⇒ Integer
Fetches the next 2 immediate bytes from memory.
- The Game Boy uses little endian format.
- This means that the first byte fetched is the least significant one.
- So if the memory has these next 2 bytes: $50 $01, the word is: $0150
91 92 93 94 95 96 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 91 def fetch_next_word lsb = fetch_next_byte msb = fetch_next_byte (msb << 8) | lsb end |
#halt ⇒ void
This method returns an undefined value.
161 162 163 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 161 def halt @halted = true end |
#handle_interrupts ⇒ void
This method returns an undefined value.
Is only called if IME and any interrupt is pending. Takes 5 cycles to complete.
174 175 176 177 178 179 180 181 182 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 174 def handle_interrupts @m_cycles = @advance_cycle.call @m_cycles = @advance_cycle.call @ime = false stack_push(value: @registers.pc) address_vector = @interrupts.priority_vector @interrupts.priority_service jump_to(address: address_vector) end |
#internal_processing ⇒ void
This method returns an undefined value.
Emulates CPU internal processing which advance cycles without Bus access.
166 167 168 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 166 def internal_processing @m_cycles = @advance_cycle.call end |
#jump_to(address:) ⇒ void
This method returns an undefined value.
Jumps execution to a given address by setting the address value into the PC.
121 122 123 124 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 121 def jump_to(address:) @registers.pc = address internal_processing end |
#log_state(old_pc, old_cycles, instruction) ⇒ void
This method returns an undefined value.
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 199 def log_state(old_pc, old_cycles, instruction) return unless @trace_cpu $stdout.printf( '%<cycles>04d | PC: $%<pc>04X | %<im>-14s (took %<ic>d) | ' \ '$%<b1>02X $%<b2>02X $%<b3>02X | F: %<f>04b | ' \ "A: $%<a>02X BC: $%<bc>04X DE: $%<de>04X HL: $%<hl>04X | [HL]: $%<mem_hl>02X\n", cycles: @m_cycles, pc: old_pc, im: instruction.mnemonic, ic: @m_cycles - old_cycles, b1: @bus.read_byte(address: old_pc), b2: @bus.read_byte(address: old_pc + 1), b3: @bus.read_byte(address: old_pc + 2), f: @registers.f >> 4, a: @registers.a, bc: @registers.bc, de: @registers.de, hl: @registers.hl, mem_hl: @bus.read_byte(address: @registers.hl) ) end |
#sign_value(byte) ⇒ Integer
Converts an unsigned byte into a value between -128 to 127 to use as an offset.
129 130 131 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 129 def sign_value(byte) byte >= 128 ? (byte - 256) : byte end |
#stack_pop ⇒ Integer
Pops a 16-bit value from the Stack.
109 110 111 112 113 114 115 116 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 109 def stack_pop lsb = bus_read(address: @registers.sp) @registers.sp += 1 msb = bus_read(address: @registers.sp) @registers.sp += 1 (msb << 8) | lsb end |
#stack_push(value:) ⇒ void
This method returns an undefined value.
Pushes a 16-bit value into the Stack.
101 102 103 104 105 106 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 101 def stack_push(value:) @registers.sp -= 1 bus_write(address: @registers.sp, value: (value >> 8) & 0xFF) @registers.sp -= 1 bus_write(address: @registers.sp, value: value & 0xFF) end |
#step ⇒ void
This method returns an undefined value.
Core CPU loop:
- Checks IME and any interrupts pending to be serviced.
- Fetches the current Opcode at the Program Counter.
- Decodes which instruction based on the Opcode fetched.
- Executes the instruction.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 35 def step if @interrupts.any_pending? @halted = false if @halted if @ime handle_interrupts return end end if @ime_scheduled @ime = true @ime_scheduled = false end if @halted @m_cycles = @advance_cycle.call return end old_pc = @registers.pc old_cycles = @m_cycles @opcode = fetch_next_byte decode_instruction execute_instruction log_state(old_pc, old_cycles, @instruction) end |
#sub16(value1, value2) ⇒ Integer
Performs a subtraction envolving a 16-bit value, CPU consumes an additional cycle to handle 16-bit values.
144 145 146 147 148 149 |
# File 'lib/amaterasu/game_boy/cpu.rb', line 144 def sub16(value1, value2) result = value1 - value2 internal_processing result end |