Skip to content

Commit a5acdc9

Browse files
authored
Merge pull request #17 from Rodrigodd/wave-trace
Implementing output wave trace of the emulation in VCD format
2 parents 35c2798 + 69aca8f commit a5acdc9

12 files changed

Lines changed: 603 additions & 65 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ log.txt
66
test_rom/test.o
77
core/test_output
88
core/tests/gameboy-test-roms
9+
core/vcd_trace
910
license/license.html
1011
android/app/release
1112
gameroy.log

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ harness = false
1818

1919
[features]
2020
io_trace = []
21+
wave_trace = ["dep:vcd"]
2122

2223
[dependencies]
24+
vcd = { version = "0.7.0", optional = true }
2325

2426
[dev-dependencies]
2527
image = { version = "0.25.4", default-features = false, features = ["png"] }

core/src/bin/run.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use gameroy::{
2+
consts::CLOCK_SPEED,
3+
gameboy::{cartridge::Cartridge, GameBoy},
4+
interpreter::Interpreter,
5+
};
6+
7+
fn parse_timeout(timeout: &str) -> Option<u64> {
8+
Some(if let Some(value) = timeout.strip_suffix("s") {
9+
value.parse::<u64>().ok()? * CLOCK_SPEED
10+
} else if let Some(value) = timeout.strip_suffix("ms") {
11+
value.parse::<u64>().ok()? * CLOCK_SPEED / 1000
12+
} else {
13+
timeout.parse::<u64>().ok()?
14+
})
15+
}
16+
17+
fn main() {
18+
let mut args = std::env::args();
19+
let mut boot_rom_path = None;
20+
let mut rom_path = None;
21+
let mut timeout = CLOCK_SPEED; // 1 second
22+
23+
// Skip program name
24+
let _ = args.next();
25+
26+
while let Some(arg) = args.next() {
27+
if arg == "--boot" {
28+
boot_rom_path = Some(args.next().expect("Missing arg value"));
29+
} else if arg == "--timeout" {
30+
timeout = parse_timeout(&args.next().expect("Missing arg value"))
31+
.expect("Invalid timeout value");
32+
} else if arg.starts_with("--") {
33+
panic!("Unknown argument: {}", arg);
34+
} else {
35+
rom_path = Some(arg);
36+
}
37+
}
38+
39+
println!(
40+
"Running ROM: {}",
41+
rom_path.as_ref().expect("No rom path provided")
42+
);
43+
println!("Boot ROM: {}", boot_rom_path.as_deref().unwrap_or("None"));
44+
45+
let rom = std::fs::read(rom_path.expect("No rom path provided")).unwrap();
46+
let boot_rom = boot_rom_path.map(|path| {
47+
std::fs::read(&path)
48+
.unwrap()
49+
.try_into()
50+
.expect("Boot ROM must be 256 bytes")
51+
});
52+
53+
let cartridge = Cartridge::new(rom).expect("Invalid ROM");
54+
55+
println!(
56+
"Cartridge: {:} ({})",
57+
cartridge.kind_name(),
58+
cartridge.header.cartridge_type
59+
);
60+
61+
let mut gameboy = GameBoy::new(boot_rom, cartridge);
62+
63+
let mut inter = Interpreter(&mut gameboy);
64+
65+
while inter.0.clock_count < timeout {
66+
inter.interpret_op();
67+
if inter.0.read(inter.0.cpu.pc) == 0x40 {
68+
println!("LD B, B detected, stopping");
69+
break;
70+
}
71+
}
72+
#[cfg(feature = "wave_trace")]
73+
{
74+
inter.0.update_all();
75+
println!("VCD committed on end: {}", inter.0.clock_count);
76+
inter.0.vcd_writer.commit().unwrap();
77+
}
78+
}

core/src/gameboy.rs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ pub struct GameBoy {
7171
/// trace of reads and writes. (kind | ((clock_count & !3) >> 1), address, value), kind: 0=GameBoy::IO_READ,1=GameBoy::IO_WRITE
7272
#[cfg(feature = "io_trace")]
7373
pub io_trace: RefCell<Vec<(u8, u16, u8)>>,
74+
75+
/// VCD writer for the waveform tracing.
76+
#[cfg(feature = "wave_trace")]
77+
pub vcd_writer: crate::wave_trace::WaveTrace,
7478
}
7579

7680
impl std::fmt::Debug for GameBoy {
@@ -181,13 +185,12 @@ impl GameBoy {
181185

182186
#[cfg(feature = "io_trace")]
183187
io_trace: Vec::new().into(),
188+
189+
#[cfg(feature = "wave_trace")]
190+
vcd_writer: crate::wave_trace::WaveTrace::new().unwrap(),
184191
};
185192

186-
if this.boot_rom.is_none() {
187-
this.reset_after_boot();
188-
} else {
189-
this.reset();
190-
}
193+
this.reset();
191194

192195
this
193196
}
@@ -226,6 +229,14 @@ impl GameBoy {
226229
self.reset_after_boot();
227230
return;
228231
}
232+
self.reset_at_power_on();
233+
}
234+
235+
/// Reset the gameboy to its state after powering on, before the boot rom is executed, even if
236+
/// there is no boot rom present.
237+
///
238+
/// Only used internally for setting a trace point in clock_count = 0.
239+
pub(crate) fn reset_at_power_on(&mut self) {
229240
// TODO: Maybe I should reset the cartridge
230241
self.cpu = Cpu::default();
231242
self.wram = [0xFF; 0x2000];
@@ -260,6 +271,9 @@ impl GameBoy {
260271
ime: cpu::ImeState::Disabled,
261272
halt_bug: false,
262273
state: cpu::CpuState::Running,
274+
275+
#[cfg(feature = "wave_trace")]
276+
op: 0,
263277
};
264278

265279
self.wram = [0xFF; 0x2000];
@@ -349,6 +363,12 @@ impl GameBoy {
349363

350364
/// Advance the clock by 'count' cycles
351365
pub fn tick(&mut self, count: u64) {
366+
#[cfg(feature = "wave_trace")]
367+
{
368+
self.vcd_writer
369+
.trace_gameboy(self.clock_count, self)
370+
.unwrap();
371+
}
352372
self.clock_count += count;
353373
}
354374

@@ -406,6 +426,11 @@ impl GameBoy {
406426
self.update_timer();
407427
self.update_serial();
408428
self.update_sound();
429+
430+
#[cfg(feature = "wave_trace")]
431+
self.vcd_writer
432+
.trace_gameboy(self.clock_count, self)
433+
.unwrap();
409434
}
410435

411436
fn update_ppu(&self) {
@@ -424,7 +449,11 @@ impl GameBoy {
424449
}
425450

426451
fn update_timer(&self) {
427-
if self.timer.borrow_mut().update(self.clock_count) {
452+
if self.timer.borrow_mut().update(
453+
self.clock_count,
454+
#[cfg(feature = "wave_trace")]
455+
&self.vcd_writer,
456+
) {
428457
self.interrupt_flag
429458
.set(self.interrupt_flag.get() | (1 << 2));
430459
}

core/src/gameboy/cartridge.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,16 @@ impl MbcSpecification {
255255
}
256256
Ok(size) => size,
257257
Err(err) => {
258-
let size = *ROM_SIZES.iter().find(|&&x| x >= rom.len()).unwrap();
259-
writeln!(error, "{}, deducing size from ROM size as {}", err, size,).unwrap();
260-
size
258+
match ROM_SIZES.iter().copied().find(|&x| x >= rom.len()) {
259+
Some(size) => {
260+
writeln!(error, "{}, deducing size from ROM size as {}", err, size,).unwrap();
261+
size
262+
}
263+
None => {
264+
writeln!(error, "{}", err).unwrap();
265+
return None;
266+
}
267+
}
261268
}
262269
};
263270

core/src/gameboy/cpu.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ pub struct Cpu {
9999
pub ime: ImeState,
100100
pub state: CpuState,
101101
pub halt_bug: bool,
102+
103+
/// The current opcode being executed. This is only used for debugging in the VCD trace.
104+
#[cfg(feature = "wave_trace")]
105+
pub op: u8,
102106
}
103107
impl fmt::Display for Cpu {
104108
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

core/src/gameboy/ppu.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -320,19 +320,19 @@ pub struct Ppu {
320320
pub state: u8,
321321
/// When making the LY==LYC comparison, uses this value instead of ly to control the comparison
322322
/// timing. This is 0xFF if this will not update the stat.
323-
ly_for_compare: u8,
323+
pub ly_for_compare: u8,
324324

325325
/// A rising edge on this signal causes a STAT interrupt.
326-
stat_signal: bool,
327-
ly_compare_signal: bool,
326+
pub stat_signal: bool,
327+
pub ly_compare_signal: bool,
328328
/// use this value instead of the current stat mode when controlling the stat interrupt signal,
329329
/// to control the timing. 0xff means that this will not trigger a interrupt.
330330
///
331331
/// Mode 0 - Horizontal Blank
332332
/// Mode 1 - Vertical Blank
333333
/// Mode 2 - OAM Search
334334
/// Mode 3 - Drawing pixels
335-
stat_mode_for_interrupt: u8,
335+
pub stat_mode_for_interrupt: u8,
336336

337337
/// Which clock cycle the PPU where last updated
338338
pub last_clock_count: u64,
@@ -374,7 +374,7 @@ pub struct Ppu {
374374
/// The x position in the current scanline, from -(8 + scx%8) to 160. Negative values
375375
/// (represented by positives between 241 and 255) are use for detecting sprites that starts
376376
/// to the left of the screen, and for discarding pixels for scrolling.
377-
scanline_x: u8,
377+
pub scanline_x: u8,
378378
}
379379

380380
fn dbg_fmt_hash<T: std::hash::Hash>(value: &T) -> impl std::fmt::Debug {
@@ -942,6 +942,9 @@ impl Ppu {
942942
// Writing to wx do some time traveling shenanigans. Make sure they are not observable.
943943
debug_assert!(ppu.last_clock_count <= gb.clock_count);
944944

945+
#[cfg(feature = "wave_trace")]
946+
gb.vcd_writer.trace_ppu(ppu.last_clock_count, ppu).unwrap();
947+
945948
ppu.last_clock_count = gb.clock_count;
946949

947950
if ppu.lcdc & 0x80 == 0 {
@@ -959,9 +962,15 @@ impl Ppu {
959962

960963
if ppu.next_clock_count >= gb.clock_count {
961964
Self::update_dma(gb, ppu, gb.clock_count);
965+
966+
#[cfg(feature = "wave_trace")]
967+
gb.vcd_writer.trace_ppu(gb.clock_count, ppu).unwrap();
962968
}
963969

964970
while ppu.next_clock_count < gb.clock_count {
971+
#[cfg(feature = "wave_trace")]
972+
let curr_ppu_clock_count = ppu.next_clock_count;
973+
965974
Self::update_dma(gb, ppu, ppu.next_clock_count);
966975
// println!("state: {}", state);
967976
match ppu.state {
@@ -1026,7 +1035,10 @@ impl Ppu {
10261035
6 => {
10271036
ppu.line_start_clock_count = ppu.next_clock_count;
10281037
ppu.screen_x = 0;
1029-
if gb.clock_count > ppu.next_clock_count + 456 {
1038+
1039+
let use_optimization = !cfg!(feature = "wave_trace");
1040+
1041+
if use_optimization && gb.clock_count > ppu.next_clock_count + 456 {
10301042
if ppu.wy == ppu.ly {
10311043
ppu.reach_window = true;
10321044
}
@@ -1518,6 +1530,9 @@ impl Ppu {
15181530
}
15191531
_ => unreachable!(),
15201532
}
1533+
1534+
#[cfg(feature = "wave_trace")]
1535+
gb.vcd_writer.trace_ppu(curr_ppu_clock_count, ppu).unwrap();
15211536
}
15221537

15231538
Self::update_dma(gb, ppu, gb.clock_count);

core/src/gameboy/timer.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,15 @@ impl Timer {
9797
///
9898
/// Update the Timer to the given `clock_count`, in O(1). See [Timer::update_cycle_by_cycle] for
9999
/// a more straigh-forward implementation.
100-
pub fn update(&mut self, clock_count: u64) -> bool {
100+
#[cfg_attr(feature = "wave_trace", allow(unreachable_code))]
101+
pub fn update(
102+
&mut self,
103+
clock_count: u64,
104+
#[cfg(feature = "wave_trace")] vcd_writer: &crate::wave_trace::WaveTrace,
105+
) -> bool {
106+
#[cfg(feature = "wave_trace")]
107+
return self.update_cycle_by_cycle(clock_count, vcd_writer);
108+
101109
debug_assert!(clock_count >= self.last_clock_count);
102110
if clock_count <= self.last_clock_count {
103111
self.next_interrupt = self.estimate_next_interrupt();
@@ -189,7 +197,11 @@ impl Timer {
189197
/// Return true if there is a interrupt
190198
///
191199
/// Reference implementation to [Self::update]. Slower, but less prone to bugs.
192-
pub fn update_cycle_by_cycle(&mut self, clock_count: u64) -> bool {
200+
pub fn update_cycle_by_cycle(
201+
&mut self,
202+
clock_count: u64,
203+
#[cfg(feature = "wave_trace")] vcd_writer: &crate::wave_trace::WaveTrace,
204+
) -> bool {
193205
let mut interrupt = false;
194206

195207
for _clock in self.last_clock_count..clock_count {
@@ -219,6 +231,9 @@ impl Timer {
219231
}
220232

221233
self.last_counter_bit = counter_bit;
234+
235+
#[cfg(feature = "wave_trace")]
236+
vcd_writer.trace_timer(_clock, self).unwrap();
222237
}
223238

224239
self.last_clock_count = clock_count;
@@ -305,7 +320,7 @@ impl Timer {
305320
}
306321
}
307322

308-
#[cfg(test)]
323+
#[cfg(all(test, not(feature = "wave_trace")))]
309324
mod tests {
310325
use super::*;
311326
use rand::Rng;

0 commit comments

Comments
 (0)