Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initialize CPU and hardware registers to post-bootrom values #50

Merged
merged 2 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 37 additions & 5 deletions fpt-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use std::fs;

use clap::{Args, Parser, Subcommand};
use clap::{Args, Parser, Subcommand, ValueEnum};
use debugger::DebuggerTextInterface;
use fpt::Gameboy;
use rustyline::error::ReadlineError;
Expand All @@ -14,10 +14,39 @@ pub mod debugger;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(flatten)]
gameboy_config: GameboyConfig,
#[command(subcommand)]
command: Commands,
}

#[derive(Clone, Debug, Args)]
struct GameboyConfig {
/// Apply known CPU and hardware register values of a well-known bootrom when it
/// hands off the execution to the cartridge's code. This skips emulating a bootrom.
#[arg(short, long)]
fake_bootrom: Option<BootromToFake>,
}

impl GameboyConfig {
/// Build a `Gameboy` following this configuration. Consumes self.
pub fn build_gameboy(self: Self) -> Gameboy {
let mut gameboy = Gameboy::new();
match self.fake_bootrom {
Some(BootromToFake::DMG0) => {
gameboy.simulate_dmg0_bootrom_handoff_state();
}
None => {}
}
gameboy
}
}

#[derive(ValueEnum, Debug, Clone, PartialEq)]
enum BootromToFake {
DMG0,
}

#[derive(Subcommand, Debug)]
enum Commands {
Debug {},
Expand Down Expand Up @@ -93,9 +122,10 @@ fn dump(args: Dump) -> Result<()> {
}
}

fn run(args: Run) -> Result<()> {
let mut gameboy = Gameboy::new();
let rom = fs::read(args.rom).unwrap();
fn run(gb_config: GameboyConfig, args: Run) -> Result<()> {
let mut gameboy = gb_config.build_gameboy();

let rom = fs::read(args.rom)?;
gameboy.load_rom(&rom);
loop {
if args.debug.unwrap_or(false) {
Expand All @@ -107,9 +137,11 @@ fn run(args: Run) -> Result<()> {

fn main() -> Result<()> {
let args = Cli::parse();
let gb_config = args.gameboy_config;

match args.command {
Commands::Debug {} => debug(),
Commands::Dump(args) => dump(args),
Commands::Run(args) => run(args),
Commands::Run(args) => run(gb_config, args),
}
}
29 changes: 26 additions & 3 deletions fpt-egui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use std::time::Duration;

use clap::Parser;
use clap::{Parser, ValueEnum};
use eframe::Frame;
use egui::{
menu, CentralPanel, Color32, ColorImage, Context, Grid, Key, RichText, ScrollArea, SidePanel,
Expand Down Expand Up @@ -125,7 +125,11 @@ impl Default for FPT {

impl FPT {
/// Called once before the first frame.
pub fn new(_cc: &eframe::CreationContext, rom_path: &str) -> Self {
pub fn new(
_cc: &eframe::CreationContext,
fake_bootrom: Option<BootromToFake>,
rom_path: &str,
) -> Self {
let mut app = FPT::default();
#[cfg(not(target_arch = "wasm32"))]
if std::env::var("CI").is_err() {
Expand All @@ -135,6 +139,13 @@ impl FPT {
panic!("Unable to open {}", rom_path);
}
}
// XXX duplicated logic from fpt-cli main.rs
match fake_bootrom {
Some(BootromToFake::DMG0) => {
app.gb.simulate_dmg0_bootrom_handoff_state();
}
None => {}
}
app
}

Expand Down Expand Up @@ -586,10 +597,21 @@ impl eframe::App for FPT {
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
/// rom
/// Apply known CPU and hardware register values of a well-known bootrom when it
/// hands off the execution to the cartridge's code. This skips emulating a bootrom.
#[arg(short, long)]
fake_bootrom: Option<BootromToFake>,
/// ROM path
rom: Option<String>,
}

// XXX duplicated struct from fpt-cli's main.rs
#[derive(ValueEnum, Debug, Clone, PartialEq)]
enum BootromToFake {
DMG0,
}

/// Desktop entry point
#[cfg(not(target_arch = "wasm32"))]
fn main() -> eframe::Result<()> {
let cli = Cli::parse();
Expand All @@ -609,6 +631,7 @@ fn main() -> eframe::Result<()> {
Box::new(|cc| {
Box::new(FPT::new(
cc,
cli.fake_bootrom,
&cli.rom.unwrap_or("roms/Tetris_World_Rev_1.gb".to_string()),
))
}),
Expand Down
55 changes: 55 additions & 0 deletions fpt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,61 @@ impl Gameboy {
}
}

/// Sets CPU and hardware registers to the values found in the DMG0 column in the tables at
/// https://gbdev.io/pandocs/Power_Up_Sequence.html#console-state-after-boot-rom-hand-off
pub fn simulate_dmg0_bootrom_handoff_state(&mut self) {
// CPU registers
self.cpu.set_af(0x0100);
self.cpu.set_bc(0xff13);
self.cpu.set_de(0x00c1);
self.cpu.set_hl(0x8403);
self.cpu.set_sp(0xfffe);
self.cpu.set_pc(0x100); // This effectively skips the bootrom

// HW registers
self.bus.write(0xFF00, 0xCF); // P1
self.bus.write(0xFF01, 0x00); // SB
self.bus.write(0xFF02, 0x7E); // SC
self.bus.write(0xFF04, 0x18); // DIV
self.bus.write(0xFF05, 0x00); // TIMA
self.bus.write(0xFF06, 0x00); // TMA
self.bus.write(0xFF07, 0xF8); // TAC
self.bus.write(0xFF0F, 0xE1); // IF
self.bus.write(0xFF10, 0x80); // NR10
self.bus.write(0xFF11, 0xBF); // NR11
self.bus.write(0xFF12, 0xF3); // NR12
self.bus.write(0xFF13, 0xFF); // NR13
self.bus.write(0xFF14, 0xBF); // NR14
self.bus.write(0xFF16, 0x3F); // NR21
self.bus.write(0xFF17, 0x00); // NR22
self.bus.write(0xFF18, 0xFF); // NR23
self.bus.write(0xFF19, 0xBF); // NR24
self.bus.write(0xFF1A, 0x7F); // NR30
self.bus.write(0xFF1B, 0xFF); // NR31
self.bus.write(0xFF1C, 0x9F); // NR32
self.bus.write(0xFF1D, 0xFF); // NR33
self.bus.write(0xFF1E, 0xBF); // NR34
self.bus.write(0xFF20, 0xFF); // NR41
self.bus.write(0xFF21, 0x00); // NR42
self.bus.write(0xFF22, 0x00); // NR43
self.bus.write(0xFF23, 0xBF); // NR44
self.bus.write(0xFF24, 0x77); // NR50
self.bus.write(0xFF25, 0xF3); // NR51
self.bus.write(0xFF26, 0xF1); // NR52
self.bus.write(0xFF40, 0x91); // LCDC
self.bus.write(0xFF41, 0x81); // STAT
self.bus.write(0xFF42, 0x00); // SCY
self.bus.write(0xFF43, 0x00); // SCX
self.bus.write(0xFF44, 0x91); // LY
self.bus.write(0xFF45, 0x00); // LYC
self.bus.write(0xFF46, 0xFF); // DMA
self.bus.write(0xFF47, 0xFC); // BGP
self.bus.write(0xFF48, 0x00); // OBP0
self.bus.write(0xFF49, 0x00); // OBP1
self.bus.write(0xFF4A, 0x00); // WY
self.bus.write(0xFF4B, 0x00); // WX
}

pub fn load_rom(&mut self, rom: &[u8]) {
self.bus.load_cartridge(rom);
}
Expand Down
12 changes: 6 additions & 6 deletions fpt/src/lr35902.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ impl DebugInterface for LR35902 {
impl LR35902 {
pub fn new(memory: Bus) -> Self {
Self {
af: 0x0100,
bc: 0xff13,
de: 0x00c1,
hl: 0x8403,
sp: 0xfffe,
pc: 0x100,
af: 0,
bc: 0,
de: 0,
hl: 0,
sp: 0,
pc: 0,
ime: false,
imenc: false,
prefix_cb: false,
Expand Down