use anyhow::Result;
use core::fmt::Write as _;
use crate::{
asm::disassemble,
bus::Bus,
clock::Clock,
cpu::{Cpu, State},
memory::Memory,
regs::Reg::*,
rom::Rom,
};
pub const ROM_DATA: &[u8] = include_bytes!("../sys/rx82_rom.bin");
pub trait Device {
fn tick(&mut self, bus: &mut Bus);
}
#[non_exhaustive]
pub struct Snapshot {
pub bus: Bus,
pub state: State,
pub tick: u16,
}
#[non_exhaustive]
pub struct System {
pub bus: Bus,
pub clock: Box<dyn Device>,
pub cpu: Cpu,
pub cycles: u16,
pub debug: bool,
pub devices: Vec<Box<dyn Device>>,
pub history: Vec<Snapshot>,
pub mem: Memory,
}
impl Default for System {
#[inline]
fn default() -> Self {
let mut sys = Self {
bus: Bus::default(),
clock: Box::new(Clock::default()),
cpu: Cpu::default(),
debug: false,
devices: Vec::new(),
history: Vec::new(),
mem: Memory::default(),
cycles: 0,
};
let data = Vec::from(ROM_DATA);
let rom = Rom {
start: 0xC000,
end: 0xFFFF,
data,
};
sys.devices.push(Box::new(rom));
sys
}
}
impl System {
#[expect(clippy::cast_possible_truncation, reason = "truncation is correct")]
#[inline]
pub fn debug_print(&mut self) {
let next = self.disassemble_next();
println!(" PC SP A B C D E F G H ZC | NEXT");
println!(
"{:04X} {:04X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:1b}{:1b} | {}",
self.cpu.pc,
self.cpu.regs.get(SP),
self.cpu.regs.get(A) as u8,
self.cpu.regs.get(B) as u8,
self.cpu.regs.get(C) as u8,
self.cpu.regs.get(D) as u8,
self.cpu.regs.get(E) as u8,
self.cpu.regs.get(F) as u8,
self.cpu.regs.get(G) as u8,
self.cpu.regs.get(H) as u8,
u8::from(self.cpu.flags.zero),
u8::from(self.cpu.flags.carry),
next,
);
}
#[inline]
#[must_use]
pub fn disassemble_next(&mut self) -> String {
let code = vec![
self.peek_mem(self.cpu.pc),
self.peek_mem(self.cpu.pc.wrapping_add(1)),
self.peek_mem(self.cpu.pc.wrapping_add(2)),
];
disassemble(&code)
}
#[inline]
pub fn peek_mem(&mut self, addr: u16) -> u8 {
let halted = self.cpu.halt;
let bus_state = self.bus.clone();
self.cpu.halt = true;
self.bus.addr = addr;
self.bus.mem = true;
self.bus.write = false;
self.tick();
self.cpu.halt = halted;
let data = self.bus.data;
self.bus = bus_state;
data
}
#[inline]
pub fn reset(&mut self) {
self.cpu.reset(&mut self.bus);
self.run();
}
#[inline]
pub fn run(&mut self) {
self.cpu.halt = false;
while !self.cpu.halt {
self.tick();
}
}
#[inline]
pub fn run_program(&mut self, program: &[u8]) -> Result<()> {
self.mem.load(0x0100, program)?;
self.cpu.pc = 0x0100;
self.run();
Ok(())
}
#[inline]
pub fn tick(&mut self) {
let state = self.cpu.state; self.cpu.tick(&mut self.bus);
for device in &mut self.devices {
device.tick(&mut self.bus);
}
self.mem.tick(&mut self.bus);
self.bus.reconcile();
if self.debug {
self.history.push(Snapshot {
tick: self.cycles,
state, bus: self.bus.clone(),
});
}
self.clock.tick(&mut self.bus);
self.cycles = self.cycles.wrapping_add(1);
}
#[expect(clippy::non_ascii_literal, reason = "looks nice")]
#[expect(clippy::unwrap_used, reason = "panic is okay here")]
#[inline]
pub fn trace(&self) {
if self.history.is_empty() {
return;
}
for chunk in self.history.chunks(16) {
let mut tick = String::from("TICK ");
let mut header = String::from("─────");
let mut state = String::from("CPU ");
let mut addr = String::from("ADDR ");
let mut data = String::from("DATA ");
let mut mem = String::from("/MEM ");
let mut wrt = String::from("/WRT ");
for snapshot in chunk {
write!(tick, " {:04X}", snapshot.tick).unwrap();
write!(header, "─────").unwrap();
write!(state, " {}", snapshot.state).unwrap();
write!(addr, " {:04X}", snapshot.bus.addr).unwrap();
write!(data, " ──{:02X}", snapshot.bus.data).unwrap();
write!(
mem,
"{}",
if snapshot.bus.mem {
" ─MEM"
} else {
" ────"
}
)
.unwrap();
write!(
wrt,
"{}",
if snapshot.bus.write {
" ─WRT"
} else {
" ────"
}
)
.unwrap();
}
println!("{tick}");
println!("{header}");
println!("{state}");
println!("{addr}");
println!("{data}");
println!("{mem}");
println!("{wrt}");
println!();
}
}
#[inline]
pub fn trace_program(&mut self, program: &[u8]) -> Result<()> {
self.debug = true;
self.history = Vec::new();
self.run_program(program)?;
self.trace();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::instructions::InstructionKind::*;
#[expect(clippy::unwrap_used, reason = "test")]
#[test]
fn trace_formatting_copes_with_long_lines() {
let mut sys = System::default();
let mut nops = vec![u8::from(Nop); 7];
nops.push(u8::from(Halt));
sys.trace_program(&nops).unwrap();
}
}