Start modeling the Lcd Display
Former-commit-id: 544f185c6f9eead870032170292b1cc8afc724bf
This commit is contained in:
parent
95f45e55a9
commit
1747addcd3
|
@ -1,6 +1,8 @@
|
|||
use crate::arm7tdmi::bus::Bus;
|
||||
use crate::arm7tdmi::{Addr, CpuState};
|
||||
use crate::disass::Disassembler;
|
||||
use crate::ioregs::consts::*;
|
||||
use crate::lcd::*;
|
||||
use crate::GBAError;
|
||||
|
||||
use super::palette_view::create_palette_view;
|
||||
|
@ -20,8 +22,10 @@ pub enum DisassMode {
|
|||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Command {
|
||||
Info,
|
||||
DisplayInfo,
|
||||
Step(usize),
|
||||
Continue,
|
||||
Frame(usize),
|
||||
HexDump(Addr, usize),
|
||||
Disass(DisassMode, Addr, usize),
|
||||
AddBreakpoint(Addr),
|
||||
|
@ -38,6 +42,21 @@ impl Command {
|
|||
use Command::*;
|
||||
match *self {
|
||||
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) => {
|
||||
for _ in 0..count {
|
||||
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) => {
|
||||
let bytes = debugger.gba.sysbus.get_bytes(addr);
|
||||
hexdump::hexdump(&bytes[0..nbytes]);
|
||||
|
@ -189,6 +214,7 @@ impl Debugger {
|
|||
|
||||
match command.as_ref() {
|
||||
"i" | "info" => Ok(Command::Info),
|
||||
"dispinfo" => Ok(Command::DisplayInfo),
|
||||
"s" | "step" => {
|
||||
let count = match args.len() {
|
||||
0 => 1,
|
||||
|
@ -202,6 +228,18 @@ impl Debugger {
|
|||
Ok(Command::Step(count as usize))
|
||||
}
|
||||
"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" => {
|
||||
let (addr, n) = match args.len() {
|
||||
2 => {
|
||||
|
|
64
src/gba.rs
64
src/gba.rs
|
@ -15,11 +15,11 @@ pub struct GameBoyAdvance {
|
|||
pub sysbus: SysBus,
|
||||
|
||||
// io devices
|
||||
lcd: Lcd,
|
||||
dma0: DmaChannel,
|
||||
dma1: DmaChannel,
|
||||
dma2: DmaChannel,
|
||||
dma3: DmaChannel,
|
||||
pub lcd: Lcd,
|
||||
pub dma0: DmaChannel,
|
||||
pub dma1: DmaChannel,
|
||||
pub dma2: DmaChannel,
|
||||
pub dma3: DmaChannel,
|
||||
|
||||
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> {
|
||||
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 cycles = self.cpu.cycles - previous_cycles;
|
||||
let (dma_cycles, _) = self.dma0.step(cycles, &mut self.sysbus);
|
||||
let cycles = cycles + dma_cycles;
|
||||
let mut cycles = self.cpu.cycles - previous_cycles;
|
||||
|
||||
let cycles = self.cpu.cycles - previous_cycles;
|
||||
let (dma_cycles, _) = self.dma1.step(cycles, &mut self.sysbus);
|
||||
let cycles = cycles + dma_cycles;
|
||||
// // drop interrupts at the moment
|
||||
|
||||
let cycles = self.cpu.cycles - previous_cycles;
|
||||
let (dma_cycles, _) = self.dma2.step(cycles, &mut self.sysbus);
|
||||
let cycles = cycles + dma_cycles;
|
||||
// let (dma_cycles, _) = self.dma0.step(cycles, &mut self.sysbus);
|
||||
// cycles += dma_cycles;
|
||||
|
||||
let cycles = self.cpu.cycles - previous_cycles;
|
||||
let (dma_cycles, _) = self.dma3.step(cycles, &mut self.sysbus);
|
||||
let cycles = cycles + dma_cycles;
|
||||
// let (dma_cycles, _) = self.dma1.step(cycles, &mut self.sysbus);
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
220
src/lcd.rs
220
src/lcd.rs
|
@ -1,4 +1,6 @@
|
|||
use super::arm7tdmi::Bus;
|
||||
use super::ioregs::consts::*;
|
||||
use super::palette::{Palette, Rgb15};
|
||||
use super::*;
|
||||
|
||||
use crate::bit::BitIndex;
|
||||
|
@ -6,12 +8,12 @@ use crate::num::FromPrimitive;
|
|||
|
||||
#[derive(Debug, Primitive)]
|
||||
enum BGMode {
|
||||
Mode0 = 0,
|
||||
Mode1 = 1,
|
||||
Mode2 = 2,
|
||||
Mode3 = 3,
|
||||
Mode4 = 4,
|
||||
Mode5 = 5,
|
||||
BGMode0 = 0,
|
||||
BGMode1 = 1,
|
||||
BGMode2 = 2,
|
||||
BGMode3 = 3,
|
||||
BGMode4 = 4,
|
||||
BGMode5 = 5,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -21,10 +23,7 @@ pub struct DisplayControl {
|
|||
hblank_interval_free: bool,
|
||||
obj_character_vram_mapping: bool, // true - 1 dimentional, false - 2 dimentional
|
||||
forced_blank: bool,
|
||||
disp_bg0: bool,
|
||||
disp_bg1: bool,
|
||||
disp_bg2: bool,
|
||||
disp_bg3: bool,
|
||||
disp_bg: [bool; 4],
|
||||
disp_obj: bool,
|
||||
disp_window0: bool,
|
||||
disp_window1: bool,
|
||||
|
@ -40,10 +39,7 @@ impl From<u16> for DisplayControl {
|
|||
hblank_interval_free: v.bit(5),
|
||||
obj_character_vram_mapping: v.bit(6),
|
||||
forced_blank: v.bit(7),
|
||||
disp_bg0: v.bit(8),
|
||||
disp_bg1: v.bit(9),
|
||||
disp_bg2: v.bit(10),
|
||||
disp_bg3: v.bit(11),
|
||||
disp_bg: [v.bit(8), v.bit(9), v.bit(10), v.bit(11)],
|
||||
disp_obj: v.bit(12),
|
||||
disp_window0: v.bit(13),
|
||||
disp_window1: v.bit(14),
|
||||
|
@ -61,6 +57,7 @@ pub struct DisplayStatus {
|
|||
hblank_irq_enable: bool,
|
||||
vcount_irq_enable: bool,
|
||||
vcount_setting: u8,
|
||||
raw_value: u16,
|
||||
}
|
||||
|
||||
impl From<u16> for DisplayStatus {
|
||||
|
@ -74,35 +71,210 @@ impl From<u16> for DisplayStatus {
|
|||
vcount_irq_enable: v.bit(5),
|
||||
// bits 6-7 are unused in GBA
|
||||
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)]
|
||||
pub struct Lcd {
|
||||
cycles: usize,
|
||||
state: LcdState,
|
||||
current_scanline: usize, // VCOUNT
|
||||
}
|
||||
|
||||
impl Lcd {
|
||||
const DISPLAY_WIDTH: usize = 240;
|
||||
const DISPLAY_HEIGHT: usize = 160;
|
||||
pub const DISPLAY_WIDTH: usize = 240;
|
||||
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 {
|
||||
Lcd {
|
||||
state: HDraw,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn palette(&self, sysbus: &SysBus) -> Palette {
|
||||
Palette::from(sysbus.get_bytes(0x0500_0000))
|
||||
}
|
||||
|
||||
impl EmuIoDev for Lcd {
|
||||
fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> (usize, Option<Interrupt>) {
|
||||
self.cycles += cycles;
|
||||
fn update_regs(&self, dispstat: DisplayStatus, sysbus: &mut SysBus) {
|
||||
let mut v = dispstat.raw_value;
|
||||
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));
|
||||
|
||||
// TODO
|
||||
(0, None)
|
||||
}
|
||||
}
|
||||
// dispstat.vcount_flag = dispstat.vcount_setting as usize == self.current_scanline;
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
|
|
Reference in a new issue