mirror of
https://github.com/mattrberry/crab.git
synced 2025-01-16 03:41:18 +01:00
get timers working for tonc tmr_demo and krom timer demo
This commit is contained in:
parent
f26fe9faf0
commit
3c13802653
2 changed files with 87 additions and 82 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue