Class: Amaterasu::GameBoy::Dma

Inherits:
Object
  • Object
show all
Defined in:
lib/amaterasu/game_boy/dma.rb

Overview

Models the RAM chip within the Game Boy.

On the real Game Boy, the CPU and DMA share the same physical bus. During DMA, the DMA controller physically takes over the bus lines — the CPU is electrically disconnected from the bus (except HRAM, which is on a separate internal path).

Constant Summary collapse

OAM_START_ADDRESS =
0xFE00
DMA_START_DELAY =
1
DMA_TOTAL_CYCLES =
160 + DMA_START_DELAY

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(bus, trace_dma: false) ⇒ Dma

Returns a new instance of Dma.



17
18
19
20
21
22
23
24
25
26
27
# File 'lib/amaterasu/game_boy/dma.rb', line 17

def initialize(bus, trace_dma: false)
  @bus = bus
  @trace_dma = trace_dma

  @internal_latch = 0xFF
  @source_address = nil
  @target_address = OAM_START_ADDRESS
  @status = :inactive
  @active = false
  @cycles = 0
end

Instance Attribute Details

#internal_latchObject (readonly)

Returns the value of attribute internal_latch.



15
16
17
# File 'lib/amaterasu/game_boy/dma.rb', line 15

def internal_latch
  @internal_latch
end

Instance Method Details

#active?Boolean

Returns Checks if the DMA transfer is in progress.

Returns:

  • (Boolean)

    Checks if the DMA transfer is in progress.



76
77
78
# File 'lib/amaterasu/game_boy/dma.rb', line 76

def active?
  @active
end

#request_transfer(source_value:) ⇒ Object

When a value is written to 0xFF46, it means a DMA transfer was requested. The value represents the upper byte of the source address. This value is saved (latched), if there is a read from 0xFF46, it should return this value. Requesting a transfer with value 0x80, means the source address is 0x8000. You can multiple the given value by 0x100 to get the source address.

Parameters:

  • source_value (Integer)

    A 8-bit value that represents the upper byte of the source address.



68
69
70
71
72
73
# File 'lib/amaterasu/game_boy/dma.rb', line 68

def request_transfer(source_value:)
  @internal_latch = source_value
  @source_address = source_value * 0x100
  @status = :pending
  @cycles = 0
end

#tickObject

This method is called once per M-cycle, CPU drives this.

  • DMA transfer 1 byte per M-cycle, totalling 160 bytes.

  • It fills the OAM memory range from 0xFE00 - 0xFE9F.



33
34
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
# File 'lib/amaterasu/game_boy/dma.rb', line 33

def tick
  case @status
  when :inactive then nil
  when :pending
    log_state if @trace_dma
    @cycles += 1
    start_transfer
  when :transferring
    @active = true
    source_byte = bus_read(address: @source_address)
    bus_write(address: @target_address, value: source_byte)

    log_state if @trace_dma

    @source_address += 1
    @target_address += 1
    @cycles += 1

    complete_transfer if @cycles == DMA_TOTAL_CYCLES
  when :completed
    @cycles = 0
    @source_address = nil
    @target_address = OAM_START_ADDRESS
    @status = :inactive
    @active = false
  end
end