mirror of
https://github.com/mattrberry/crab.git
synced 2025-02-11 20:48:40 +01:00
scheduler, ppu vcount/dispstat on scheduler, ppu io fix read/write order
This commit is contained in:
parent
71235e4b2a
commit
4a2f0a6789
4 changed files with 94 additions and 30 deletions
|
@ -1,5 +1,6 @@
|
||||||
require "./types"
|
require "./types"
|
||||||
require "./util"
|
require "./util"
|
||||||
|
require "./scheduler"
|
||||||
require "./cartridge"
|
require "./cartridge"
|
||||||
require "./mmio"
|
require "./mmio"
|
||||||
require "./bus"
|
require "./bus"
|
||||||
|
@ -9,6 +10,7 @@ require "./display"
|
||||||
require "./ppu"
|
require "./ppu"
|
||||||
|
|
||||||
class GBA
|
class GBA
|
||||||
|
getter scheduler : Scheduler
|
||||||
getter cartridge : Cartridge
|
getter cartridge : Cartridge
|
||||||
getter mmio : MMIO { MMIO.new self }
|
getter mmio : MMIO { MMIO.new self }
|
||||||
getter bus : Bus { Bus.new self }
|
getter bus : Bus { Bus.new self }
|
||||||
|
@ -17,10 +19,10 @@ class GBA
|
||||||
getter display : Display { Display.new }
|
getter display : Display { Display.new }
|
||||||
getter ppu : PPU { PPU.new self }
|
getter ppu : PPU { PPU.new self }
|
||||||
|
|
||||||
@cycles = 0
|
|
||||||
|
|
||||||
def initialize(rom_path : String)
|
def initialize(rom_path : String)
|
||||||
|
@scheduler = Scheduler.new
|
||||||
@cartridge = Cartridge.new rom_path
|
@cartridge = Cartridge.new rom_path
|
||||||
|
handle_events
|
||||||
|
|
||||||
SDL.init(SDL::Init::VIDEO | SDL::Init::AUDIO | SDL::Init::JOYSTICK)
|
SDL.init(SDL::Init::VIDEO | SDL::Init::AUDIO | SDL::Init::JOYSTICK)
|
||||||
LibSDL.joystick_open 0
|
LibSDL.joystick_open 0
|
||||||
|
@ -28,6 +30,7 @@ class GBA
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_events : Nil
|
def handle_events : Nil
|
||||||
|
@scheduler.schedule PPU::REFRESH, ->handle_events
|
||||||
while event = SDL::Event.poll
|
while event = SDL::Event.poll
|
||||||
case event
|
case event
|
||||||
when SDL::Event::Quit then exit 0
|
when SDL::Event::Quit then exit 0
|
||||||
|
@ -46,11 +49,6 @@ class GBA
|
||||||
end
|
end
|
||||||
|
|
||||||
def tick(cycles : Int) : Nil
|
def tick(cycles : Int) : Nil
|
||||||
ppu.tick cycles
|
@scheduler.tick cycles
|
||||||
@cycles += cycles
|
|
||||||
if @cycles >= PPU::REFRESH
|
|
||||||
handle_events
|
|
||||||
@cycles -= PPU::REFRESH
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,10 +5,10 @@ class Interrupts
|
||||||
|
|
||||||
def read_io(io_addr : Int) : Byte
|
def read_io(io_addr : Int) : Byte
|
||||||
case io_addr
|
case io_addr
|
||||||
when 0x200 then (@reg_ie >> 8).to_u8
|
when 0x200 then 0xFF_u8 & @reg_ie
|
||||||
when 0x201 then @reg_ie.to_u8!
|
when 0x201 then 0xFF_u8 & @reg_ie >> 8
|
||||||
when 0x202 then (@reg_if >> 8).to_u8
|
when 0x202 then 0xFF_u8 & @reg_if
|
||||||
when 0x203 then @reg_if.to_u8!
|
when 0x203 then 0xFF_u8 & @reg_if >> 8
|
||||||
when 0x208 then @reg_ime ? 1_u8 : 0_u8
|
when 0x208 then @reg_ime ? 1_u8 : 0_u8
|
||||||
when 0x209 then @reg_ime ? 1_u8 : 0_u8
|
when 0x209 then @reg_ime ? 1_u8 : 0_u8
|
||||||
else raise "Unimplemented interrupts read ~ addr:#{hex_str io_addr.to_u8}"
|
else raise "Unimplemented interrupts read ~ addr:#{hex_str io_addr.to_u8}"
|
||||||
|
@ -17,10 +17,10 @@ class Interrupts
|
||||||
|
|
||||||
def write_io(io_addr : Int, value : Byte) : Nil
|
def write_io(io_addr : Int, value : Byte) : Nil
|
||||||
case io_addr
|
case io_addr
|
||||||
when 0x200 then @reg_ie = (@reg_ie & 0x00FF) | value.to_u16 << 8
|
when 0x200 then @reg_ie = (@reg_ie & 0xFF00) | value
|
||||||
when 0x201 then @reg_ie = (@reg_ie & 0xFF00) | value
|
when 0x201 then @reg_ie = (@reg_ie & 0x00FF) | value.to_u16 << 8
|
||||||
when 0x202 then @reg_ie = (@reg_if & 0x00FF) | value.to_u16 << 8
|
when 0x202 then @reg_ie = (@reg_if & 0xFF00) | value
|
||||||
when 0x203 then @reg_ie = (@reg_if & 0xFF00) | value
|
when 0x203 then @reg_ie = (@reg_if & 0x00FF) | value.to_u16 << 8
|
||||||
when 0x208 then @reg_ime = bit?(value, 0)
|
when 0x208 then @reg_ime = bit?(value, 0)
|
||||||
when 0x209 then @reg_ime = bit?(value, 0)
|
when 0x209 then @reg_ime = bit?(value, 0)
|
||||||
else raise "Unimplemented interrupts write ~ addr:#{hex_str io_addr.to_u8!}, val:#{value}"
|
else raise "Unimplemented interrupts write ~ addr:#{hex_str io_addr.to_u8!}, val:#{value}"
|
||||||
|
|
|
@ -41,38 +41,61 @@ class PPU
|
||||||
|
|
||||||
getter dispcnt : DISPCNT = DISPCNT.new 0
|
getter dispcnt : DISPCNT = DISPCNT.new 0
|
||||||
getter dispstat : DISPSTAT = DISPSTAT.new 0
|
getter dispstat : DISPSTAT = DISPSTAT.new 0
|
||||||
|
getter vcount : UInt16 = 0x0000_u16
|
||||||
@cycles = 0
|
|
||||||
|
|
||||||
def initialize(@gba : GBA)
|
def initialize(@gba : GBA)
|
||||||
|
start_scanline
|
||||||
end
|
end
|
||||||
|
|
||||||
def tick(cycles : Int) : Nil
|
def start_scanline : Nil
|
||||||
@cycles += cycles
|
@gba.scheduler.schedule 960, ->start_hblank
|
||||||
if @cycles >= REFRESH
|
end
|
||||||
|
|
||||||
|
def start_hblank : Nil
|
||||||
|
@gba.scheduler.schedule 272, ->end_hblank
|
||||||
|
@dispstat.hblank = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def end_hblank : Nil
|
||||||
|
@dispstat.hblank = false
|
||||||
|
@vcount += 1
|
||||||
|
if @vcount == 228
|
||||||
|
@vcount = 0
|
||||||
|
@dispstat.vblank = false
|
||||||
|
@gba.scheduler.schedule 0, ->start_scanline
|
||||||
|
elsif @vcount == 160
|
||||||
|
@dispstat.vblank = true
|
||||||
|
@gba.scheduler.schedule 0, ->start_vblank_line
|
||||||
@gba.display.draw @vram
|
@gba.display.draw @vram
|
||||||
@cycles -= REFRESH
|
elsif @vcount >= 160
|
||||||
|
@gba.scheduler.schedule 0, ->start_vblank_line
|
||||||
|
else
|
||||||
|
@gba.scheduler.schedule 0, ->start_scanline
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def start_vblank_line : Nil
|
||||||
|
@gba.scheduler.schedule 960, ->start_hblank
|
||||||
|
end
|
||||||
|
|
||||||
def read_io(io_addr : Int) : Byte
|
def read_io(io_addr : Int) : Byte
|
||||||
case io_addr
|
case io_addr
|
||||||
when 0x000 then 0xFF_u8 & @dispcnt.value >> 8
|
when 0x000 then 0xFF_u8 & @dispcnt.value
|
||||||
when 0x001 then 0xFF_u8 & @dispcnt.value
|
when 0x001 then 0xFF_u8 & @dispcnt.value >> 8
|
||||||
when 0x004 then 0xFF_u8 & @dispstat.value >> 8
|
when 0x004 then 0xFF_u8 & @dispstat.value
|
||||||
when 0x005 then 0xFF_u8 & @dispstat.value
|
when 0x005 then 0xFF_u8 & @dispstat.value >> 8
|
||||||
else raise "Unimplemented PPU read ~ addr:#{hex_str io_addr.to_u8}"
|
else raise "Unimplemented PPU read ~ addr:#{hex_str io_addr.to_u8}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def write_io(io_addr : Int, value : Byte) : Nil
|
def write_io(io_addr : Int, value : Byte) : Nil
|
||||||
case io_addr
|
case io_addr
|
||||||
when 0x000 then @dispcnt.value = (@dispcnt.value & 0x00FF) | value.to_u16 << 8
|
when 0x000 then @dispcnt.value = (@dispcnt.value & 0xFF00) | value
|
||||||
when 0x001 then @dispcnt.value = (@dispcnt.value & 0xFF00) | value
|
when 0x001 then @dispcnt.value = (@dispcnt.value & 0x00FF) | value.to_u16 << 8
|
||||||
when 0x002 # undocumented - green swap
|
when 0x002 # undocumented - green swap
|
||||||
when 0x003 # undocumented - green swap
|
when 0x003 # undocumented - green swap
|
||||||
when 0x004 then @dispstat.value = (@dispstat.value & 0x00FF) | value.to_u16 << 8
|
when 0x004 then @dispstat.value = (@dispstat.value & 0xFF00) | value
|
||||||
when 0x005 then @dispstat.value = (@dispstat.value & 0xFF00) | value
|
when 0x005 then @dispstat.value = (@dispstat.value & 0x00FF) | value.to_u16 << 8
|
||||||
else raise "Unimplemented PPU write ~ addr:#{hex_str io_addr.to_u8}, val:#{value}"
|
else raise "Unimplemented PPU write ~ addr:#{hex_str io_addr.to_u8}, val:#{value}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
43
src/crab/scheduler.cr
Normal file
43
src/crab/scheduler.cr
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
class Scheduler
|
||||||
|
private record Event, cycles : UInt64, proc : Proc(Void)
|
||||||
|
|
||||||
|
@events : Deque(Event) = Deque(Event).new 10
|
||||||
|
@cycles : UInt64 = 0
|
||||||
|
|
||||||
|
def schedule(cycles : Int, proc : Proc(Void)) : Nil
|
||||||
|
self << Event.new @cycles + cycles, proc
|
||||||
|
end
|
||||||
|
|
||||||
|
def schedule(cycles : Int, &block : ->)
|
||||||
|
self << Event.new @cycles + cycles, block
|
||||||
|
end
|
||||||
|
|
||||||
|
@[AlwaysInline]
|
||||||
|
def <<(event : Event) : Nil
|
||||||
|
idx = @events.bsearch_index { |e| e.cycles > event.cycles }
|
||||||
|
unless idx.nil?
|
||||||
|
@events.insert(idx, event)
|
||||||
|
else
|
||||||
|
@events << event
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def tick(cycles : Int) : Nil
|
||||||
|
cycles.times do
|
||||||
|
@cycles += 1
|
||||||
|
call_current
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def call_current : Nil
|
||||||
|
loop do
|
||||||
|
event = @events.first?
|
||||||
|
if event && @cycles >= event.cycles
|
||||||
|
event.proc.call
|
||||||
|
@events.shift
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Add table
Reference in a new issue