Start modeling the Lcd Display

Former-commit-id: 544f185c6f9eead870032170292b1cc8afc724bf
This commit is contained in:
Michel Heily 2019-07-11 18:17:28 +03:00
parent 95f45e55a9
commit 1747addcd3
3 changed files with 277 additions and 45 deletions

View file

@ -1,6 +1,8 @@
use crate::arm7tdmi::bus::Bus; use crate::arm7tdmi::bus::Bus;
use crate::arm7tdmi::{Addr, CpuState}; use crate::arm7tdmi::{Addr, CpuState};
use crate::disass::Disassembler; use crate::disass::Disassembler;
use crate::ioregs::consts::*;
use crate::lcd::*;
use crate::GBAError; use crate::GBAError;
use super::palette_view::create_palette_view; use super::palette_view::create_palette_view;
@ -20,8 +22,10 @@ pub enum DisassMode {
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum Command { pub enum Command {
Info, Info,
DisplayInfo,
Step(usize), Step(usize),
Continue, Continue,
Frame(usize),
HexDump(Addr, usize), HexDump(Addr, usize),
Disass(DisassMode, Addr, usize), Disass(DisassMode, Addr, usize),
AddBreakpoint(Addr), AddBreakpoint(Addr),
@ -38,6 +42,21 @@ impl Command {
use Command::*; use Command::*;
match *self { match *self {
Info => println!("{}", debugger.gba.cpu), Info => println!("{}", debugger.gba.cpu),
DisplayInfo => {
println!("{:?}", debugger.gba.lcd);
println!(
"DISPCNT: {:#?}",
DisplayControl::from(debugger.gba.sysbus.ioregs.read_reg(REG_DISPCNT))
);
println!(
"DISPSTAT: {:#?}",
DisplayStatus::from(debugger.gba.sysbus.ioregs.read_reg(REG_DISPSTAT))
);
println!(
"VCOUNT: {:?}",
debugger.gba.sysbus.ioregs.read_reg(REG_VCOUNT)
);
}
Step(count) => { Step(count) => {
for _ in 0..count { for _ in 0..count {
if let Some(bp) = debugger.check_breakpoint() { if let Some(bp) = debugger.check_breakpoint() {
@ -95,6 +114,12 @@ impl Command {
_ => (), _ => (),
}; };
}, },
Frame(count) => {
for _ in 0..count {
debugger.gba.frame();
print!(".")
}
}
HexDump(addr, nbytes) => { HexDump(addr, nbytes) => {
let bytes = debugger.gba.sysbus.get_bytes(addr); let bytes = debugger.gba.sysbus.get_bytes(addr);
hexdump::hexdump(&bytes[0..nbytes]); hexdump::hexdump(&bytes[0..nbytes]);
@ -189,6 +214,7 @@ impl Debugger {
match command.as_ref() { match command.as_ref() {
"i" | "info" => Ok(Command::Info), "i" | "info" => Ok(Command::Info),
"dispinfo" => Ok(Command::DisplayInfo),
"s" | "step" => { "s" | "step" => {
let count = match args.len() { let count = match args.len() {
0 => 1, 0 => 1,
@ -202,6 +228,18 @@ impl Debugger {
Ok(Command::Step(count as usize)) Ok(Command::Step(count as usize))
} }
"c" | "continue" => Ok(Command::Continue), "c" | "continue" => Ok(Command::Continue),
"f" | "frame" => {
let count = match args.len() {
0 => 1,
1 => self.val_number(&args[0])?,
_ => {
return Err(DebuggerError::InvalidCommandFormat(
"frame [count]".to_string(),
))
}
};
Ok(Command::Frame(count as usize))
}
"x" | "hexdump" => { "x" | "hexdump" => {
let (addr, n) = match args.len() { let (addr, n) = match args.len() {
2 => { 2 => {

View file

@ -15,11 +15,11 @@ pub struct GameBoyAdvance {
pub sysbus: SysBus, pub sysbus: SysBus,
// io devices // io devices
lcd: Lcd, pub lcd: Lcd,
dma0: DmaChannel, pub dma0: DmaChannel,
dma1: DmaChannel, pub dma1: DmaChannel,
dma2: DmaChannel, pub dma2: DmaChannel,
dma3: DmaChannel, pub dma3: DmaChannel,
post_bool_flags: bool, post_bool_flags: bool,
} }
@ -42,29 +42,51 @@ impl GameBoyAdvance {
} }
} }
fn run_cpu_for_n_cycles(&mut self, n: usize) {
let previous_cycles = self.cpu.cycles;
loop {
self.cpu.step_one(&mut self.sysbus).unwrap();
if n > self.cpu.cycles - previous_cycles {
break;
}
}
}
pub fn frame(&mut self) {
for _ in 0..Lcd::DISPLAY_HEIGHT {
self.run_cpu_for_n_cycles(Lcd::CYCLES_HDRAW);
let _irq = self.lcd.set_hblank(&mut self.sysbus);
self.run_cpu_for_n_cycles(Lcd::CYCLES_HBLANK);
}
let _irq = self.lcd.set_vblank(&mut self.sysbus);
self.run_cpu_for_n_cycles(Lcd::CYCLES_VBLANK);
self.lcd.render(&mut self.sysbus); // Currently not implemented
self.lcd.set_hdraw();
}
pub fn step(&mut self) -> GBAResult<DecodedInstruction> { pub fn step(&mut self) -> GBAResult<DecodedInstruction> {
let previous_cycles = self.cpu.cycles; let previous_cycles = self.cpu.cycles;
let decoded = self.cpu.step_one(&mut self.sysbus)?; let executed_insn = self.cpu.step_one(&mut self.sysbus)?;
// drop interrupts at the moment let mut cycles = self.cpu.cycles - previous_cycles;
let cycles = self.cpu.cycles - previous_cycles;
let (dma_cycles, _) = self.dma0.step(cycles, &mut self.sysbus);
let cycles = cycles + dma_cycles;
let cycles = self.cpu.cycles - previous_cycles; // // drop interrupts at the moment
let (dma_cycles, _) = self.dma1.step(cycles, &mut self.sysbus);
let cycles = cycles + dma_cycles;
let cycles = self.cpu.cycles - previous_cycles; // let (dma_cycles, _) = self.dma0.step(cycles, &mut self.sysbus);
let (dma_cycles, _) = self.dma2.step(cycles, &mut self.sysbus); // cycles += dma_cycles;
let cycles = cycles + dma_cycles;
let cycles = self.cpu.cycles - previous_cycles; // let (dma_cycles, _) = self.dma1.step(cycles, &mut self.sysbus);
let (dma_cycles, _) = self.dma3.step(cycles, &mut self.sysbus); // cycles += dma_cycles;
let cycles = cycles + dma_cycles;
let (lcd_cycles, _) = self.lcd.step(cycles, &mut self.sysbus); // let (dma_cycles, _) = self.dma2.step(cycles, &mut self.sysbus);
// cycles += dma_cycles;
Ok(decoded) // return the decoded instruction for the debugger // let (dma_cycles, _) = self.dma3.step(cycles, &mut self.sysbus);
// cycles += dma_cycles;
// let (lcd_cycles, _) = self.lcd.step(cycles, &mut self.sysbus);
// cycles += lcd_cycles;
Ok(executed_insn)
} }
} }

View file

@ -1,4 +1,6 @@
use super::arm7tdmi::Bus;
use super::ioregs::consts::*; use super::ioregs::consts::*;
use super::palette::{Palette, Rgb15};
use super::*; use super::*;
use crate::bit::BitIndex; use crate::bit::BitIndex;
@ -6,12 +8,12 @@ use crate::num::FromPrimitive;
#[derive(Debug, Primitive)] #[derive(Debug, Primitive)]
enum BGMode { enum BGMode {
Mode0 = 0, BGMode0 = 0,
Mode1 = 1, BGMode1 = 1,
Mode2 = 2, BGMode2 = 2,
Mode3 = 3, BGMode3 = 3,
Mode4 = 4, BGMode4 = 4,
Mode5 = 5, BGMode5 = 5,
} }
#[derive(Debug)] #[derive(Debug)]
@ -21,10 +23,7 @@ pub struct DisplayControl {
hblank_interval_free: bool, hblank_interval_free: bool,
obj_character_vram_mapping: bool, // true - 1 dimentional, false - 2 dimentional obj_character_vram_mapping: bool, // true - 1 dimentional, false - 2 dimentional
forced_blank: bool, forced_blank: bool,
disp_bg0: bool, disp_bg: [bool; 4],
disp_bg1: bool,
disp_bg2: bool,
disp_bg3: bool,
disp_obj: bool, disp_obj: bool,
disp_window0: bool, disp_window0: bool,
disp_window1: bool, disp_window1: bool,
@ -40,10 +39,7 @@ impl From<u16> for DisplayControl {
hblank_interval_free: v.bit(5), hblank_interval_free: v.bit(5),
obj_character_vram_mapping: v.bit(6), obj_character_vram_mapping: v.bit(6),
forced_blank: v.bit(7), forced_blank: v.bit(7),
disp_bg0: v.bit(8), disp_bg: [v.bit(8), v.bit(9), v.bit(10), v.bit(11)],
disp_bg1: v.bit(9),
disp_bg2: v.bit(10),
disp_bg3: v.bit(11),
disp_obj: v.bit(12), disp_obj: v.bit(12),
disp_window0: v.bit(13), disp_window0: v.bit(13),
disp_window1: v.bit(14), disp_window1: v.bit(14),
@ -61,6 +57,7 @@ pub struct DisplayStatus {
hblank_irq_enable: bool, hblank_irq_enable: bool,
vcount_irq_enable: bool, vcount_irq_enable: bool,
vcount_setting: u8, vcount_setting: u8,
raw_value: u16,
} }
impl From<u16> for DisplayStatus { impl From<u16> for DisplayStatus {
@ -74,35 +71,210 @@ impl From<u16> for DisplayStatus {
vcount_irq_enable: v.bit(5), vcount_irq_enable: v.bit(5),
// bits 6-7 are unused in GBA // bits 6-7 are unused in GBA
vcount_setting: v.bit_range(8..15) as u8, vcount_setting: v.bit_range(8..15) as u8,
raw_value: v,
} }
} }
} }
#[derive(Debug)]
pub struct BgControl {
bg_priority: u8,
character_base_block: u8,
moasic: bool,
colors_palettes: bool, // 0=16/16, 1=256/1)
screen_base_block: u8,
wraparound: bool,
screen_width: usize,
screen_height: usize,
}
impl From<u16> for BgControl {
fn from(v: u16) -> Self {
let (width, height) = match v.bit_range(14..15) {
0 => (256, 256),
1 => (512, 256),
2 => (256, 512),
3 => (512, 512),
_ => unreachable!(),
};
BgControl {
bg_priority: v.bit_range(0..1) as u8,
character_base_block: v.bit_range(2..3) as u8,
moasic: v.bit(6),
colors_palettes: v.bit(7), // 0=16/16, 1=256/1)
screen_base_block: v.bit_range(8..12) as u8,
wraparound: v.bit(13),
screen_width: width,
screen_height: height,
}
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum LcdState {
HDraw = 0,
HBlank,
VBlank,
}
impl Default for LcdState {
fn default() -> LcdState {
LcdState::HDraw
}
}
use LcdState::*;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Lcd { pub struct Lcd {
cycles: usize, cycles: usize,
state: LcdState,
current_scanline: usize, // VCOUNT current_scanline: usize, // VCOUNT
} }
impl Lcd { impl Lcd {
const DISPLAY_WIDTH: usize = 240; pub const DISPLAY_WIDTH: usize = 240;
const DISPLAY_HEIGHT: usize = 160; pub const DISPLAY_HEIGHT: usize = 160;
pub const CYCLES_PIXEL: usize = 4;
pub const CYCLES_HDRAW: usize = 960;
pub const CYCLES_HBLANK: usize = 272;
pub const CYCLES_SCANLINE: usize = 1232;
pub const CYCLES_VDRAW: usize = 197120;
pub const CYCLES_VBLANK: usize = 83776;
pub fn new() -> Lcd { pub fn new() -> Lcd {
Lcd { Lcd {
state: HDraw,
..Default::default() ..Default::default()
} }
} }
fn palette(&self, sysbus: &SysBus) -> Palette {
Palette::from(sysbus.get_bytes(0x0500_0000))
} }
impl EmuIoDev for Lcd { fn update_regs(&self, dispstat: DisplayStatus, sysbus: &mut SysBus) {
fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> (usize, Option<Interrupt>) { let mut v = dispstat.raw_value;
self.cycles += cycles; v.set_bit(0, dispstat.vblank_flag);
v.set_bit(1, dispstat.hblank_flag);
v.set_bit(2, dispstat.vcount_flag);
sysbus.ioregs.write_reg(REG_DISPSTAT, v);
}
// let mut dispcnt = DisplayControl::from(sysbus.ioregs.read_reg(REG_DISPCNT)); pub fn set_hblank(&mut self, sysbus: &mut SysBus) -> Option<Interrupt> {
let dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT));
let mut v = dispstat.raw_value;
v.set_bit(1, true);
self.state = HBlank;
sysbus.ioregs.write_reg(REG_DISPSTAT, v);
if dispstat.hblank_irq_enable {
Some(Interrupt::LCD_HBlank)
} else {
None
}
}
pub fn set_vblank(&mut self, sysbus: &mut SysBus) -> Option<Interrupt> {
let dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT));
let mut v = dispstat.raw_value;
v.set_bit(1, false);
v.set_bit(0, true);
self.state = HBlank;
sysbus.ioregs.write_reg(REG_DISPSTAT, v);
if dispstat.vblank_irq_enable {
Some(Interrupt::LCD_VBlank)
} else {
None
}
}
pub fn set_hdraw(&mut self) {
self.state = HDraw;
}
fn bgcnt(&self, sysbus: &SysBus, bg: u32) -> BgControl {
BgControl::from(sysbus.ioregs.read_reg(REG_BG0CNT + 2 * bg))
}
pub fn render(&mut self, sysbus: &SysBus) {
// let dispcnt = DisplayControl::from(sysbus.ioregs.read_reg(REG_DISPCNT));
// let dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT));
// TODO - redner
}
}
// *TODO* Running the Lcd step by step causes a massive performance impact, so for now not treat it as an emulated IO device.
//
// impl EmuIoDev for Lcd {
// fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> (usize, Option<Interrupt>) {
// self.cycles += cycles;
// sysbus
// .ioregs
// .write_reg(REG_VCOUNT, self.current_scanline as u16);
// let mut dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT)); // let mut dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT));
// TODO // dispstat.vcount_flag = dispstat.vcount_setting as usize == self.current_scanline;
(0, None) // if dispstat.vcount_irq_enable {
} // panic!("VCOUNT IRQ NOT IMPL");
} // }
// match self.state {
// HDraw => {
// if self.cycles > Lcd::CYCLES_HDRAW {
// self.current_scanline += 1;
// self.cycles -= Lcd::CYCLES_HDRAW;
// let (new_state, irq) = if self.current_scanline < Lcd::DISPLAY_HEIGHT {
// // HBlank
// dispstat.hblank_flag = true;
// let irq = if dispstat.hblank_irq_enable {
// Some(Interrupt::LCD_HBlank)
// } else {
// None
// };
// (HBlank, irq)
// } else {
// dispstat.vblank_flag = true;
// let irq = if dispstat.vblank_irq_enable {
// Some(Interrupt::LCD_HBlank)
// } else {
// None
// };
// (VBlank, irq)
// };
// self.state = new_state;
// self.update_regs(dispstat, sysbus);
// return (0, irq);
// }
// }
// HBlank => {
// if self.cycles > Lcd::CYCLES_HBLANK {
// self.cycles -= Lcd::CYCLES_HBLANK;
// self.state = HDraw;
// dispstat.hblank_flag = false;
// self.update_regs(dispstat, sysbus);
// return (0, None);
// }
// }
// VBlank => {
// if self.cycles > Lcd::CYCLES_VBLANK {
// self.cycles -= Lcd::CYCLES_VBLANK;
// self.state = HDraw;
// dispstat.vblank_flag = false;
// self.current_scanline = 0;
// self.update_regs(dispstat, sysbus);
// return (0, None);
// }
// }
// }
// // let mut dispcnt = DisplayControl::from(sysbus.ioregs.read_reg(REG_DISPCNT));
// // let mut dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT));
// // TODO
// (0, None)
// }
// }