dma rewrite, fix emerald and kirby only playing on left side

This commit is contained in:
Matthew Berry 2021-01-26 00:07:07 -08:00
parent fd70aa31ca
commit 0de7d0c0c5
3 changed files with 95 additions and 70 deletions

View file

@ -33,7 +33,7 @@ class DMAChannels
end end
def timer_overflow(timer : Int) : Nil def timer_overflow(timer : Int) : Nil
(0..1).each do |channel| 2.times do |channel|
if timer == @timers[channel].call if timer == @timers[channel].call
if @sizes[channel] > 0 if @sizes[channel] > 0
log "Timer overflow good; channel:#{channel}, timer:#{timer}".colorize.fore(:yellow) log "Timer overflow good; channel:#{channel}, timer:#{timer}".colorize.fore(:yellow)
@ -45,7 +45,7 @@ class DMAChannels
@latches[channel] = 0 @latches[channel] = 0
end end
end end
@gba.dma.trigger channel + 1 if @sizes[channel] < 16 @gba.dma.trigger_fifo(channel) if @sizes[channel] < 16
end end
end end

View file

@ -1,17 +1,23 @@
class DMA class DMA
class DMACNT < BitField(UInt16) enum StartTiming
bool enable Immediate = 0
bool irq_enable VBlank = 1
num start_timing, 2 HBlank = 2
bool game_pak Special = 3
num type, 1 end
bool repeat
num source_control, 2
num dest_control, 2
num not_used, 5
def to_s(io) enum AddressControl
io << "enable:#{enable}, irq:#{irq_enable}, timing:#{start_timing}, game_pak:#{game_pak}, type:#{type}, repeat:#{repeat}, srcctl:#{source_control}, dstctl:#{dest_control}" Increment = 0
Decrement = 1
Fixed = 2
IncrementReload = 3
def delta : Int
case self
in Increment, IncrementReload then 1
in Decrement then -1
in Fixed then 0
end
end end
end end
@ -19,7 +25,7 @@ class DMA
@dmasad = Array(UInt32).new 4, 0 @dmasad = Array(UInt32).new 4, 0
@dmadad = Array(UInt32).new 4, 0 @dmadad = Array(UInt32).new 4, 0
@dmacnt_l = Array(UInt16).new 4, 0 @dmacnt_l = Array(UInt16).new 4, 0
@dmacnt_h = Array(DMACNT).new 4 { DMACNT.new 0 } @dmacnt_h = Array(Reg::DMACNT).new 4 { Reg::DMACNT.new 0 }
@src = Array(UInt32).new 4, 0 @src = Array(UInt32).new 4, 0
@dst = Array(UInt32).new 4, 0 @dst = Array(UInt32).new 4, 0
@src_mask = [0x07FFFFFF, 0x0FFFFFFF, 0x0FFFFFFF, 0x0FFFFFFF] @src_mask = [0x07FFFFFF, 0x0FFFFFFF, 0x0FFFFFFF, 0x0FFFFFFF]
@ -28,105 +34,105 @@ class DMA
end end
def read_io(io_addr : Int) : UInt8 def read_io(io_addr : Int) : UInt8
dma_number = (io_addr - 0xB0) // 12 channel = (io_addr - 0xB0) // 12
reg = (io_addr - 0xB0) % 12 reg = (io_addr - 0xB0) % 12
case reg case reg
when 0, 1, 2, 3 # dmasad when 0, 1, 2, 3 # dmasad
(@dmasad[dma_number] >> 8 * reg).to_u8! (@dmasad[channel] >> 8 * reg).to_u8!
when 4, 5, 6, 7 # dmadad when 4, 5, 6, 7 # dmadad
(@dmadad[dma_number] >> 8 * (reg - 4)).to_u8! (@dmadad[channel] >> 8 * (reg - 4)).to_u8!
when 8, 9 # dmacnt_l when 8, 9 # dmacnt_l
(@dmacnt_l[dma_number] >> 8 * (reg - 8)).to_u8! (@dmacnt_l[channel] >> 8 * (reg - 8)).to_u8!
when 10, 11 # dmacnt_h when 10, 11 # dmacnt_h
(@dmacnt_h[dma_number].value >> 8 * (reg - 10)).to_u8! (@dmacnt_h[channel].value >> 8 * (reg - 10)).to_u8!
else abort "Unmapped DMA read ~ addr:#{hex_str io_addr.to_u8}" else abort "Unmapped DMA read ~ addr:#{hex_str io_addr.to_u8}"
end end
end end
def write_io(io_addr : Int, value : UInt8) : Nil def write_io(io_addr : Int, value : UInt8, caller = __FILE__) : Nil
dma_number = (io_addr - 0xB0) // 12 channel = (io_addr - 0xB0) // 12
reg = (io_addr - 0xB0) % 12 reg = (io_addr - 0xB0) % 12
case reg case reg
when 0, 1, 2, 3 # dmasad when 0, 1, 2, 3 # dmasad
mask = 0xFF_u32 << (8 * reg) mask = 0xFF_u32 << (8 * reg)
value = value.to_u32 << (8 * reg) value = value.to_u32 << (8 * reg)
dmasad = @dmasad[dma_number] dmasad = @dmasad[channel]
@dmasad[dma_number] = ((dmasad & ~mask) | value) & @src_mask[dma_number] @dmasad[channel] = ((dmasad & ~mask) | value) & @src_mask[channel]
when 4, 5, 6, 7 # dmadad when 4, 5, 6, 7 # dmadad
reg -= 4 reg -= 4
mask = 0xFF_u32 << (8 * reg) mask = 0xFF_u32 << (8 * reg)
value = value.to_u32 << (8 * reg) value = value.to_u32 << (8 * reg)
dmadad = @dmadad[dma_number] dmadad = @dmadad[channel]
@dmadad[dma_number] = ((dmadad & ~mask) | value) & @dst_mask[dma_number] @dmadad[channel] = ((dmadad & ~mask) | value) & @dst_mask[channel]
when 8, 9 # dmacnt_l when 8, 9 # dmacnt_l
reg -= 8 reg -= 8
mask = 0xFF_u32 << (8 * reg) mask = 0xFF_u32 << (8 * reg)
value = value.to_u16 << (8 * reg) value = value.to_u16 << (8 * reg)
dmacnt_l = @dmacnt_l[dma_number] dmacnt_l = @dmacnt_l[channel]
@dmacnt_l[dma_number] = ((dmacnt_l & ~mask) | value) & @len_mask[dma_number] @dmacnt_l[channel] = ((dmacnt_l & ~mask) | value) & @len_mask[channel]
when 10, 11 # dmacnt_h when 10, 11 # dmacnt_h
reg -= 10 reg -= 10
mask = 0xFF_u32 << (8 * reg) mask = 0xFF_u32 << (8 * reg)
value = value.to_u16 << (8 * reg) value = value.to_u16 << (8 * reg)
dmacnt_h = @dmacnt_h[dma_number] dmacnt_h = @dmacnt_h[channel]
enabled = dmacnt_h.enable enabled = dmacnt_h.enable
dmacnt_h.value = (dmacnt_h.value & ~mask) | value dmacnt_h.value = (dmacnt_h.value & ~mask) | value
if dmacnt_h.enable && !enabled if dmacnt_h.enable && !enabled
@src[dma_number], @dst[dma_number] = @dmasad[dma_number], @dmadad[dma_number] @src[channel], @dst[channel] = @dmasad[channel], @dmadad[channel]
trigger dma_number, on_write: true trigger channel if dmacnt_h.start_timing == StartTiming::Immediate.value
end end
else abort "Unmapped DMA write ~ addr:#{hex_str io_addr.to_u8}, val:#{value}".colorize(:yellow) else abort "Unmapped DMA write ~ addr:#{hex_str io_addr.to_u8}, val:#{value}".colorize(:yellow)
end end
end end
# todo: clean up _all_ of the trigger logic. this is nasty.
# todo: vdma # todo: vdma
def trigger_hdma : Nil def trigger_hdma : Nil
4.times do |channel| 4.times do |channel|
dmacnt_h = @dmacnt_h[channel] dmacnt_h = @dmacnt_h[channel]
if dmacnt_h.start_timing == 2 && dmacnt_h.enable trigger channel if dmacnt_h.enable && dmacnt_h.start_timing == StartTiming::HBlank.value
trigger channel
dmacnt_h.enable = false unless dmacnt_h.repeat
end
end end
end end
def trigger(channel : Int, on_write = false) : Nil # todo: maybe abstract these various triggers
def trigger_fifo(fifo_channel : Int) : Nil
dmacnt_h = @dmacnt_h[fifo_channel + 1]
trigger fifo_channel + 1 if dmacnt_h.enable && dmacnt_h.start_timing == StartTiming::Special.value
end
def trigger(channel : Int) : Nil
dmacnt_h = @dmacnt_h[channel] dmacnt_h = @dmacnt_h[channel]
log "DMA channel ##{channel} enabled, #{hex_str @dmasad[channel]} -> #{hex_str @dmadad[channel]}, len: #{hex_str @dmacnt_l[channel]}"
log " DMACNT: #{dmacnt_h.to_s}" start_timing = StartTiming.from_value(dmacnt_h.start_timing)
log " TM#{@gba.apu.soundcnt_h.dma_sound_a_timer}CNT: #{@gba.timer.tmcnt[@gba.apu.soundcnt_h.dma_sound_a_timer]}" source_control = AddressControl.from_value(dmacnt_h.source_control)
if dmacnt_h.start_timing == 0 || !on_write dest_control = AddressControl.from_value(dmacnt_h.dest_control)
special = dmacnt_h.start_timing == 3 word_size = 2 << dmacnt_h.type # 2 or 4 bytes
fifo_dma = special && 1 <= channel <= 2
delta = 2 << dmacnt_h.type # transfer either halfwords or words
delta = 4 if fifo_dma # fifo audio always transfers in words
ds = delta * case dmacnt_h.source_control
when 0 then 1
when 1 then -1
when 2 then 0
when 3 then puts "Prohibited source control".colorize.fore(:red); 1
else abort "Impossible source control: #{dmacnt_h.source_control}"
end
dd = delta * case dmacnt_h.dest_control
when 0 then 1
when 1 then -1
when 2 then 0
when 3 then 1
else abort "Impossible source control: #{dmacnt_h.dest_control}"
end
dd = 0 if fifo_dma # fifo audio doesn't increment destination
len = @dmacnt_l[channel] len = @dmacnt_l[channel]
len = 4 if fifo_dma
log " Starting transfer of #{len} #{"half" if dmacnt_h.type == 0}words from #{hex_str @src[channel]} to #{hex_str @dst[channel]}" puts "Prohibited source address control".colorize.fore(:yellow) if source_control == AddressControl::IncrementReload
if start_timing == StartTiming::Special
if channel == 1 || channel == 2 # fifo
len = 4
word_size = 4
dest_control = AddressControl::Fixed
elsif channel == 3 # video capture
puts "todo: video capture dma"
else # prohibited
puts "Prohibited special dma".colorize.fore(:yellow)
end
end
delta_source = word_size * source_control.delta
delta_dest = word_size * dest_control.delta
len.times do |idx| len.times do |idx|
log "transferring #{dmacnt_h.type == 0 ? "16" : "32"} bits from #{hex_str @src[channel]} to #{hex_str @dst[channel]}" @gba.bus[@dst[channel]] = word_size == 4 ? @gba.bus.read_word(@src[channel]) : @gba.bus.read_half(@src[channel]).to_u16!
@gba.bus[@dst[channel]] = !fifo_dma && dmacnt_h.type == 0 ? @gba.bus.read_half(@src[channel]).to_u16! : @gba.bus.read_word(@src[channel]) @src[channel] += delta_source
@src[channel] += ds @dst[channel] += delta_dest
@dst[channel] += dd
end
@dst[channel] = @dmadad[channel] if dmacnt_h.dest_control == 3
dmacnt_h.enable = false unless dmacnt_h.repeat
end end
@dst[channel] = @dmadad[channel] if dest_control == AddressControl::IncrementReload
dmacnt_h.enable = false unless dmacnt_h.repeat && start_timing != StartTiming::Immediate
end end
end end

View file

@ -65,6 +65,25 @@ module Reg
bool not_used_2 bool not_used_2
end end
####################
# DMA
class DMACNT < BitField(UInt16)
bool enable
bool irq_enable
num start_timing, 2
bool game_pak
num type, 1
bool repeat
num source_control, 2
num dest_control, 2
num not_used, 5
def to_s(io)
io << "enable:#{enable}, irq:#{irq_enable}, timing:#{start_timing}, game_pak:#{game_pak}, type:#{type}, repeat:#{repeat}, srcctl:#{source_control}, dstctl:#{dest_control}"
end
end
#################### ####################
# PPU # PPU