Skip to content

Commit

Permalink
Merge branch 'joamag/run-until' into 'master'
Browse files Browse the repository at this point in the history
Support for address based testing

Closes #38

See merge request joamag/boytacean!41
  • Loading branch information
joamag committed Feb 19, 2024
2 parents 282bc17 + c5b6156 commit 67a4578
Show file tree
Hide file tree
Showing 18 changed files with 244 additions and 82 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
*.pyc
*.pyd

.DS_Store

Cargo.lock

/.vscode/settings.json

/.eggs
/.venv
/.idea

/ndk
Expand Down
11 changes: 11 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ test-rust:
- rustc --version
- cargo test

test-pyo3:
stage: test
parallel:
matrix:
- RUST_VERSION: ["1.74.0"]
script:
- rustup toolchain install $RUST_VERSION
- rustup override set $RUST_VERSION
- rustc --version
- python3 setup.py test

deploy-netlify-preview:
stage: deploy
script:
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ cargo build
pip install .
```

or

```bash
python setup.py install
```

### WASM for Node.js

```bash
Expand Down
2 changes: 1 addition & 1 deletion frontends/libretro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ pub unsafe extern "C" fn retro_load_game(game: *const RetroGameInfo) -> bool {
instance.set_mode(mode);
instance.reset();
instance.load(true);
instance.load_cartridge(rom);
instance.load_cartridge(rom).unwrap();
update_vars();
true
}
Expand Down
17 changes: 10 additions & 7 deletions frontends/sdl/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod test;
use audio::Audio;
use boytacean::{
devices::{printer::PrinterDevice, stdout::StdoutDevice},
error::Error,
gb::{AudioProvider, GameBoy, GameBoyMode},
info::Info,
pad::PadKey,
Expand Down Expand Up @@ -225,7 +226,7 @@ impl Emulator {
));
}

pub fn load_rom(&mut self, path: Option<&str>) {
pub fn load_rom(&mut self, path: Option<&str>) -> Result<(), Error> {
let rom_path: &str = path.unwrap_or(&self.rom_path);
let ram_path = replace_ext(rom_path, "sav").unwrap_or_else(|| "invalid".to_string());
let rom = self.system.load_rom_file(
Expand All @@ -235,7 +236,7 @@ impl Emulator {
} else {
None
},
);
)?;
println!(
"========= Cartridge =========\n{}\n=============================",
rom
Expand All @@ -253,12 +254,14 @@ impl Emulator {
.to_str()
.unwrap()
.to_string();
Ok(())
}

pub fn reset(&mut self) {
pub fn reset(&mut self) -> Result<(), Error> {
self.system.reset();
self.system.load(true);
self.load_rom(None);
self.load_rom(None)?;
Ok(())
}

pub fn apply_cheats(&mut self, cheats: &Vec<String>) {
Expand Down Expand Up @@ -418,7 +421,7 @@ impl Emulator {
Event::KeyDown {
keycode: Some(Keycode::R),
..
} => self.reset(),
} => self.reset().unwrap(),
Event::KeyDown {
keycode: Some(Keycode::B),
..
Expand Down Expand Up @@ -528,7 +531,7 @@ impl Emulator {
}
self.system.reset();
self.system.load(true);
self.load_rom(Some(&filename));
self.load_rom(Some(&filename)).unwrap();
}
_ => (),
}
Expand Down Expand Up @@ -989,7 +992,7 @@ fn main() {
};
let mut emulator = Emulator::new(game_boy, options);
emulator.start(SCREEN_SCALE);
emulator.load_rom(Some(&args.rom_path));
emulator.load_rom(Some(&args.rom_path)).unwrap();
emulator.apply_cheats(&args.cheats);
emulator.toggle_palette();

Expand Down
3 changes: 3 additions & 0 deletions frontends/web/ts/gb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ import info from "../package.json";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
declare const require: any;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
declare const process: any;

/**
* The frequency at which the Game Boy emulator should
* run "normally".
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
keywords="gameboy emulator rust",
url="https://boytacean.joao.me",
packages=["boytacean"],
test_suite="boytacean.test",
package_dir={"": os.path.normpath("src/python")},
rust_extensions=[
setuptools_rust.RustExtension(
Expand Down
5 changes: 5 additions & 0 deletions src/consts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Timer registers
pub const DIV_ADDR: u16 = 0xff04;
pub const TIMA_ADDR: u16 = 0xff05;
pub const TMA_ADDR: u16 = 0xff06;
pub const TAC_ADDR: u16 = 0xff07;
113 changes: 70 additions & 43 deletions src/gb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,57 +426,57 @@ impl GameBoy {
let rom = self.rom().clone();
self.reset();
self.load(true);
self.load_cartridge(rom);
}

self.load_cartridge(rom).unwrap();
}

/// Advance the clock of the system by one tick, this will
/// usually imply executing one CPU instruction and advancing
/// all the other components of the system by the required
/// amount of cycles.
///
/// This method takes into account the current speed of the
/// system (single or double) and will execute the required
/// amount of cycles in the other components of the system
/// accordingly.
///
/// The amount of cycles executed by the CPU is returned.
pub fn clock(&mut self) -> u16 {
let cycles = self.cpu_clock() as u16;
let cycles_n = cycles / self.multiplier() as u16;
if self.ppu_enabled {
self.ppu_clock(cycles_n);
}
if self.apu_enabled {
self.apu_clock(cycles_n);
}
if self.dma_enabled {
self.dma_clock(cycles);
}
if self.timer_enabled {
self.timer_clock(cycles);
}
if self.serial_enabled {
self.serial_clock(cycles);
}
self.clock_devices(cycles, cycles_n);
cycles
}

/// Risky function that will clock the CPU multiple times
/// allowing an undefined number of cycles to be executed
/// in the other Game Boy components.
///
/// This can cause unwanted behaviour in components like
/// the PPU where only one mode switch operation is expected
/// per each clock call.
pub fn clock_m(&mut self, count: usize) -> u16 {
///
/// At the end of this execution major synchronization issues
/// may arise, so use with caution.
pub fn clock_many(&mut self, count: usize) -> u16 {
let mut cycles = 0u16;
for _ in 0..count {
cycles += self.cpu_clock() as u16;
}
let cycles_n = cycles / self.multiplier() as u16;
if self.ppu_enabled {
self.ppu_clock(cycles_n);
}
if self.apu_enabled {
self.apu_clock(cycles_n);
}
if self.dma_enabled {
self.dma_clock(cycles);
}
if self.timer_enabled {
self.timer_clock(cycles);
}
if self.serial_enabled {
self.serial_clock(cycles);
self.clock_devices(cycles, cycles_n);
cycles
}

/// Function equivalent to `clock()` but that allows pre-emptive
/// breaking of the clock cycle loop if the PC (Program Counter)
/// reaches the provided address.
pub fn clock_step(&mut self, addr: u16) -> u16 {
let cycles = self.cpu_clock() as u16;
if self.cpu_i().pc() == addr {
return cycles;
}
let cycles_n = cycles / self.multiplier() as u16;
self.clock_devices(cycles, cycles_n);
cycles
}

Expand All @@ -503,14 +503,33 @@ impl GameBoy {
pub fn step_to(&mut self, addr: u16) -> u32 {
let mut cycles = 0u32;
loop {
cycles += self.clock() as u32;
cycles += self.clock_step(addr) as u32;
if self.cpu_i().pc() == addr {
break;
}
}
cycles
}

#[inline(always)]
fn clock_devices(&mut self, cycles: u16, cycles_n: u16) {
if self.ppu_enabled {
self.ppu_clock(cycles_n);
}
if self.apu_enabled {
self.apu_clock(cycles_n);
}
if self.dma_enabled {
self.dma_clock(cycles);
}
if self.timer_enabled {
self.timer_clock(cycles);
}
if self.serial_enabled {
self.serial_clock(cycles);
}
}

pub fn key_press(&mut self, key: PadKey) {
self.pad().key_press(key);
}
Expand Down Expand Up @@ -1073,24 +1092,32 @@ impl GameBoy {
Ok(())
}

pub fn load_cartridge(&mut self, rom: Cartridge) -> &mut Cartridge {
pub fn load_cartridge(&mut self, rom: Cartridge) -> Result<&mut Cartridge, Error> {
self.mmu().set_rom(rom);
self.mmu().rom()
Ok(self.mmu().rom())
}

pub fn load_rom(&mut self, data: &[u8], ram_data: Option<&[u8]>) -> &mut Cartridge {
pub fn load_rom(
&mut self,
data: &[u8],
ram_data: Option<&[u8]>,
) -> Result<&mut Cartridge, Error> {
let mut rom = Cartridge::from_data(data);
if let Some(ram_data) = ram_data {
rom.set_ram_data(ram_data)
}
self.load_cartridge(rom)
}

pub fn load_rom_file(&mut self, path: &str, ram_path: Option<&str>) -> &mut Cartridge {
let data = read_file(path).unwrap();
pub fn load_rom_file(
&mut self,
path: &str,
ram_path: Option<&str>,
) -> Result<&mut Cartridge, Error> {
let data = read_file(path)?;
match ram_path {
Some(ram_path) => {
let ram_data = read_file(ram_path).unwrap();
let ram_data = read_file(ram_path)?;
self.load_rom(&data, Some(&ram_data))
}
None => self.load_rom(&data, None),
Expand Down Expand Up @@ -1176,12 +1203,12 @@ impl GameBoy {
}));
}

pub fn load_rom_wa(&mut self, data: &[u8]) -> Cartridge {
let rom = self.load_rom(data, None);
pub fn load_rom_wa(&mut self, data: &[u8]) -> Result<Cartridge, String> {
let rom = self.load_rom(data, None).map_err(|e| e.to_string())?;
rom.set_rumble_cb(|active| {
rumble_callback(active);
});
rom.clone()
Ok(rom.clone())
}

pub fn load_callbacks_wa(&mut self) {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

pub mod apu;
pub mod cheats;
pub mod consts;
pub mod cpu;
pub mod data;
pub mod devices;
Expand Down
22 changes: 16 additions & 6 deletions src/py.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,18 @@ impl GameBoy {
.map_err(PyErr::new::<PyException, _>)
}

pub fn load_rom(&mut self, data: &[u8]) {
self.system.load_rom(data, None);
pub fn load_rom(&mut self, data: &[u8]) -> PyResult<()> {
self.system
.load_rom(data, None)
.map(|_| ())
.map_err(PyErr::new::<PyException, _>)
}

pub fn load_rom_file(&mut self, path: &str) {
self.system.load_rom_file(path, None);
pub fn load_rom_file(&mut self, path: &str) -> PyResult<()> {
self.system
.load_rom_file(path, None)
.map(|_| ())
.map_err(PyErr::new::<PyException, _>)
}

pub fn read_memory(&mut self, addr: u16) -> u8 {
Expand All @@ -66,8 +72,12 @@ impl GameBoy {
self.system.clock()
}

pub fn clock_m(&mut self, count: usize) -> u16 {
self.system.clock_m(count)
pub fn clock_many(&mut self, count: usize) -> u16 {
self.system.clock_many(count)
}

pub fn clock_step(&mut self, addr: u16) -> u16 {
self.system.clock_step(addr)
}

pub fn clocks(&mut self, count: usize) -> u64 {
Expand Down
3 changes: 2 additions & 1 deletion src/python/boytacean/boytacean.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ class GameBoy:
def read_memory(self, addr: int) -> int: ...
def write_memory(self, addr: int, value: int): ...
def clock(self) -> int: ...
def clock_m(self, count: int) -> int: ...
def clock_many(self, count: int) -> int: ...
def clock_step(self, addr: int) -> int: ...
def clocks(self, count: int) -> int: ...
def next_frame(self) -> int: ...
def step_to(self, addr: int) -> int: ...
Expand Down
Loading

0 comments on commit 67a4578

Please sign in to comment.