get timers working for tonc tmr_demo and krom timer demo

This commit is contained in:
Matthew Berry 2021-02-20 10:37:14 -08:00
parent f26fe9faf0
commit 3c13802653
2 changed files with 87 additions and 82 deletions

View file

@ -84,6 +84,22 @@ module Reg
end
end
####################
# Timer
class TMCNT < BitField(UInt16)
num not_used_1, 8, lock: true
bool enable
bool irq_enable
num not_used_2, 3, lock: true
bool cascade
num frequency, 2
def to_s(io)
io << "enable:#{enable}, irq:#{irq_enable}, cascade:#{cascade}, freq:#{frequency}"
end
end
####################
# PPU

View file

@ -1,107 +1,96 @@
class Timer
class TMCNT < BitField(UInt16)
num not_used_1, 8, lock: true
bool enable
bool irq_enable
num not_used_2, 3, lock: true
bool cascade
num frequency, 2
PERIODS = [1, 64, 256, 1024]
def to_s(io)
io << "enable:#{enable}, irq:#{irq_enable}, cascade:#{cascade}, freq:#{frequency}"
end
end
getter tmcnt
@interrupt_events : Array(Proc(Nil))
@interrupt_flags : Array(Proc(Nil))
def initialize(@gba : GBA)
@tmcnt = Array(TMCNT).new 4 { TMCNT.new 0 }
@tmd = Array(UInt16).new 4, 0 # reload values
@tm = Array(UInt16).new 4, 0 # counted values
@cycle_enabled = Array(UInt64).new 4, 0 # cycle that the timer was enabled
@events = Array(Proc(Nil)).new 4 { |i| overflow i } # overflow closures for each timer
@event_types = [Scheduler::EventType::Timer0, Scheduler::EventType::Timer1, Scheduler::EventType::Timer2, Scheduler::EventType::Timer3]
@interrupt_events = [->{ @gba.interrupts.reg_if.timer0 = true }, ->{ @gba.interrupts.reg_if.timer1 = true },
->{ @gba.interrupts.reg_if.timer2 = true }, ->{ @gba.interrupts.reg_if.timer3 = true }]
@tmcnt = Array(Reg::TMCNT).new 4 { Reg::TMCNT.new 0 } # control registers
@tmd = Array(UInt16).new 4, 0 # reload values
@tm = Array(UInt16).new 4, 0 # counted values
@cycle_enabled = Array(UInt64).new 4, 0 # cycle that the timer was enabled
@events = Array(Proc(Nil)).new 4 { |i| overflow i } # overflow closures for each timer
@event_types = [Scheduler::EventType::Timer0, Scheduler::EventType::Timer1,
Scheduler::EventType::Timer2, Scheduler::EventType::Timer3]
@interrupt_flags = [->{ @gba.interrupts.reg_if.timer0 = true }, ->{ @gba.interrupts.reg_if.timer1 = true },
->{ @gba.interrupts.reg_if.timer2 = true }, ->{ @gba.interrupts.reg_if.timer3 = true }]
end
def overflow(timer_number : Int) : Proc(Nil)
tmcnt = @tmcnt[timer_number]
def overflow(num : Int) : Proc(Nil)
->{
log "overflowed timer #{timer_number}".colorize.fore(:green)
@tm[timer_number] = @tmd[timer_number]
if timer_number < 3
next_timer_number = timer_number + 1
if @tmcnt[next_timer_number].cascade && @tmcnt[next_timer_number].enable
@tm[next_timer_number] &+= 1
@events[next_timer_number].call if @tm[next_timer_number] == 0 # tell the next timer that it has overflowed
end
@tm[num] = @tmd[num]
@cycle_enabled[num] = @gba.scheduler.cycles
if num < 3 && @tmcnt[num + 1].cascade && @tmcnt[num + 1].enable
@tm[num + 1] &+= 1
@events[num + 1].call if @tm[num + 1] == 0 # call overflow handler if cascaded timer overflowed
end
@gba.apu.timer_overflow timer_number if timer_number <= 1
@interrupt_events[timer_number].call
@gba.interrupts.schedule_interrupt_check if tmcnt.irq_enable
cycles_until_overflow = freq_to_cycles(tmcnt.frequency) * (0x10000 - @tm[timer_number])
log " scheduling overflow for timer #{timer_number} in #{cycles_until_overflow} cycles" unless tmcnt.cascade
@gba.scheduler.schedule cycles_until_overflow, @events[timer_number], @event_types[timer_number] unless tmcnt.cascade
@gba.apu.timer_overflow num if num <= 1 # alert apu of timer 0-1 overflow
if @tmcnt[num].irq_enable # set interupt flag for this timer
@interrupt_flags[num].call
@gba.interrupts.schedule_interrupt_check
end
@gba.scheduler.schedule cycles_until_overflow(num), @events[num], @event_types[num] unless @tmcnt[num].cascade
}
end
def cycles_until_overflow(num : Int) : Int32
PERIODS[@tmcnt[num].frequency] * (0x10000 - @tm[num])
end
def get_current_tm(num : Int) : UInt16
if @tmcnt[num].enable && !@tmcnt[num].cascade
elapsed = @gba.scheduler.cycles - @cycle_enabled[num]
@tm[num] &+ elapsed // PERIODS[@tmcnt[num].frequency]
else
@tm[num]
end
end
def update_tm(num : Int) : Nil
@tm[num] = get_current_tm(num)
@cycle_enabled[num] = @gba.scheduler.cycles
end
def read_io(io_addr : Int) : UInt8
timer_number = (io_addr & 0xFF) // 4
timer_control = bit?(io_addr, 1)
high = bit?(io_addr, 0)
tmcnt = @tmcnt[timer_number]
value = if timer_control
num = (io_addr & 0xF) // 4
tmcnt = @tmcnt[num]
value = if bit?(io_addr, 1)
tmcnt.value
else
elapsed = @gba.scheduler.cycles - @cycle_enabled[timer_number]
@tm[timer_number] &+= elapsed // freq_to_cycles(tmcnt.frequency) if tmcnt.enable && !tmcnt.cascade
@tm[timer_number]
get_current_tm(num)
end
value >>= 8 if high
value >>= 8 if bit?(io_addr, 0)
value.to_u8!
end
def write_io(io_addr : Int, value : UInt8) : Nil
timer_number = (io_addr & 0xFF) // 4
timer_control = bit?(io_addr, 1)
num = (io_addr & 0xF) // 4
high = bit?(io_addr, 0)
mask = 0xFF_u16
mask <<= 8 unless high
value = value.to_u16 << 8 if high
if timer_control
# todo: properly handle disabling / enabling timers via `cascade` field
tmcnt = @tmcnt[timer_number]
enabled = tmcnt.enable
tmcnt.value = (tmcnt.value & mask) | value
if tmcnt.enable && !enabled # enabled
log "Timer #{timer_number} enabled, freq: #{hex_str freq_to_cycles(tmcnt.frequency)}, tm:#{hex_str @tm[timer_number]}"
log " TMCNT: #{tmcnt.to_s}"
@cycle_enabled[timer_number] = @gba.scheduler.cycles
@tm[timer_number] = @tmd[timer_number]
log " freq_to_cycles(#{tmcnt.frequency}) -> #{hex_str freq_to_cycles(tmcnt.frequency)}, @tm[#{timer_number}] -> #{hex_str @tm[timer_number]}, 0x10000 - @tm[#{timer_number}] -> #{0x10000 - @tm[timer_number]}"
cycles_until_overflow = freq_to_cycles(tmcnt.frequency) * (0x10000 - @tm[timer_number])
log " Scheduling overflow for timer #{timer_number} in #{cycles_until_overflow} cycles"
@gba.scheduler.schedule cycles_until_overflow, @events[timer_number], @event_types[timer_number] unless tmcnt.cascade
elsif !tmcnt.enable && enabled # disabled
log "Timer #{timer_number} disabled".colorize.mode(:bold)
elapsed = @gba.scheduler.cycles - @cycle_enabled[timer_number]
@tm[timer_number] &+= elapsed // freq_to_cycles(tmcnt.frequency)
@gba.scheduler.clear @event_types[timer_number]
mask = 0xFF00_u16
if high
mask >>= 8
value = value.to_u16 << 8
end
if bit?(io_addr, 1)
unless high
update_tm(num)
tmcnt = @tmcnt[num]
was_enabled = tmcnt.enable
was_cascade = tmcnt.cascade
tmcnt.value = (tmcnt.value & mask) | value
if tmcnt.enable
if tmcnt.cascade
@gba.scheduler.clear @event_types[num]
elsif !was_enabled || was_cascade # enabled or no longer cascade
@cycle_enabled[num] = @gba.scheduler.cycles
@tm[num] = @tmd[num] if !was_enabled
@gba.scheduler.schedule cycles_until_overflow(num), @events[num], @event_types[num]
end
elsif was_enabled # disabled
@gba.scheduler.clear(@event_types[num])
end
end
else
tmd = @tmd[timer_number]
log " updating TM#{timer_number}D from #{hex_str tmd} to #{hex_str (tmd & mask) | value}"
@tmd[timer_number] = (tmd & mask) | value
end
end
def freq_to_cycles(freq : Int) : Int
case freq
when 0 then 0b1_u32
else 0b10000_u32 << (freq << 1)
@tmd[num] = (@tmd[num] & mask) | value
end
end
end