use core::fmt::{Display, Formatter};
use crate::{
instructions::{
InstructionKind::{self, Nop},
Operands,
},
regs::{Reg, Regs},
system::{Bus, Device, State},
};
#[non_exhaustive]
#[derive(Debug, Default)]
pub struct Cpu {
pub flags: Flags,
pub halt: bool,
pub ins: InstructionKind,
pub op_hi: u8,
pub op_lo: u8,
pub pc: u16,
pub phase: Phase,
pub regs: Regs,
pub target: Target,
}
impl Device for Cpu {
#[inline]
fn tick(&mut self, bus: &mut Bus) {
self.phase = match self.phase {
Phase::Decode => self.decode(bus),
Phase::Execute => self.execute(bus),
Phase::Fetch => self.fetch(bus),
Phase::Wait => self.wait(),
}
}
}
impl Cpu {
#[expect(clippy::as_conversions, reason = "easiest way to sign-extend")]
#[expect(clippy::cast_possible_wrap, reason = "i8 to u16 is sound")]
#[expect(clippy::cast_sign_loss, reason = "okay with wrapping_add")]
#[inline]
pub fn branch(&mut self, dis: u8) {
self.pc = self.pc.wrapping_add(dis as i8 as u16); }
#[inline]
pub fn decode(&mut self, bus: &mut Bus) -> Phase {
match self.target {
Target::Opcode => {
let opcode = bus.data;
self.ins = InstructionKind::try_from(opcode).unwrap_or(Nop);
match self.ins.operands() {
Operands::Zero => {
bus.pending_write.get_or_insert(vec![State::Mem(false)]);
Phase::Execute
}
Operands::One | Operands::Two => {
self.target = Target::Operand;
Phase::Fetch
}
}
}
Target::Operand => {
self.op_hi = 0;
self.op_lo = bus.data;
match self.ins.operands() {
Operands::Two => {
self.target = Target::Operand2;
Phase::Fetch
}
Operands::One => {
bus.pending_write.get_or_insert(vec![State::Mem(false)]);
Phase::Execute
}
Operands::Zero => {
unreachable!("fetched operand for zero-operand instruction")
}
}
}
Target::Operand2 => {
self.op_hi = bus.data;
bus.pending_write.get_or_insert(vec![State::Mem(false)]);
Phase::Execute
}
Target::Write(_, _) => unreachable!("reached decode phase after memory write"),
}
}
#[inline]
pub fn execute(&mut self, bus: &mut Bus) -> Phase {
let ins = self.ins.clone();
ins.execute(self);
if let Target::Write(addr, val) = self.target {
bus.pending_write.get_or_insert(vec![
State::Addr(addr),
State::Data(val),
State::Mem(true),
State::Write(true),
]);
Phase::Wait
} else {
self.target = Target::Opcode;
Phase::Fetch
}
}
#[inline]
pub fn fetch(&mut self, bus: &mut Bus) -> Phase {
if self.halt {
Phase::Fetch
} else {
bus.pending_write.get_or_insert(vec![
State::Addr(self.pc),
State::Mem(true),
State::Write(false),
]);
self.pc = self.pc.wrapping_add(1);
Phase::Wait
}
}
#[inline]
#[must_use]
pub fn op(&self) -> u16 {
u16::from_be_bytes([self.op_hi, self.op_lo])
}
#[inline]
pub fn reset(&mut self) {
*self = Self::default();
}
#[inline]
pub fn wait(&mut self) -> Phase {
match self.target {
Target::Write(_, _) => {
self.target = Target::Opcode;
Phase::Fetch
}
Target::Opcode | Target::Operand | Target::Operand2 => Phase::Decode,
}
}
#[expect(clippy::as_conversions, reason = "truncation is correct")]
#[expect(clippy::cast_possible_truncation, reason = "truncation is correct")]
#[inline]
pub fn write_mem(&mut self, addr: u16, reg: Reg) {
println!("write_mem: {reg}");
self.target = Target::Write(addr, self.regs.get(reg) as u8);
}
}
#[non_exhaustive]
#[derive(Debug, Default)]
pub struct Flags {
pub carry: bool,
pub zero: bool,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum Phase {
Decode,
Execute,
#[default]
Fetch,
Wait,
}
impl Display for Phase {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(
f,
"{}",
match *self {
Phase::Decode => "DCOD",
Phase::Execute => "EXEC",
Phase::Fetch => "FTCH",
Phase::Wait => "WAIT",
}
)
}
}
#[expect(clippy::exhaustive_enums, reason = "this actually is exhaustive")]
#[derive(Debug, Default, PartialEq)]
pub enum Target {
#[default]
Opcode,
Operand,
Operand2,
Write(u16, u8),
}
#[cfg(test)]
mod tests {
use crate::{instructions::InstructionKind::*, regs::Reg::*};
use super::*;
#[test]
fn cpu_states_are_correct_for_1_byte_instruction() {
use InstructionKind::*;
let mut cpu = Cpu::default();
let mut bus = Bus::default();
assert_eq!(cpu.phase, Phase::Fetch);
assert_eq!(cpu.target, Target::Opcode);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Wait);
bus.data = u8::from(Nop);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Decode);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Execute);
assert_eq!(cpu.pc, 0x0001);
}
#[test]
fn cpu_states_are_correct_for_2_byte_instruction() {
let mut cpu = Cpu::default();
let mut bus = Bus::default();
assert_eq!(cpu.phase, Phase::Fetch);
assert_eq!(cpu.target, Target::Opcode);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Wait);
bus.data = u8::from(LoadRegImm(A)); cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Decode);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Fetch);
assert_eq!(cpu.target, Target::Operand);
assert_eq!(cpu.pc, 0x0001);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Wait);
bus.data = 0xFF;
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Decode);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Execute);
cpu.tick(&mut bus);
assert_eq!(cpu.regs.get(A), 0x00FF);
assert_eq!(cpu.pc, 0x0002);
}
#[test]
fn cpu_states_are_correct_for_3_byte_instruction() {
let mut cpu = Cpu::default();
let mut bus = Bus::default();
assert_eq!(cpu.phase, Phase::Fetch);
assert_eq!(cpu.target, Target::Opcode);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Wait);
bus.data = u8::from(LoadRegImm(AB)); cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Decode);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Fetch);
assert_eq!(cpu.target, Target::Operand);
assert_eq!(cpu.pc, 0x0001);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Wait);
bus.data = 0xEF;
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Decode);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Fetch);
assert_eq!(cpu.target, Target::Operand2);
assert_eq!(cpu.pc, 0x0002);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Wait);
bus.data = 0xBE;
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Decode);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Execute);
cpu.tick(&mut bus);
assert_eq!(cpu.regs.get(AB), 0xBEEF);
assert_eq!(cpu.pc, 0x0003);
}
#[test]
fn cpu_states_are_correct_for_mem_write_instruction() {
let mut cpu = Cpu::default();
let mut bus = Bus::default();
cpu.regs.set(A, 0x00FF);
assert_eq!(cpu.phase, Phase::Fetch);
assert_eq!(cpu.target, Target::Opcode);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Wait);
bus.data = u8::from(StoreRegDirect(A)); cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Decode);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Fetch);
assert_eq!(cpu.target, Target::Operand);
assert_eq!(cpu.pc, 0x0001);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Wait);
bus.data = 0xEF;
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Decode);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Fetch);
assert_eq!(cpu.target, Target::Operand2);
assert_eq!(cpu.pc, 0x0002);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Wait);
bus.data = 0xBE;
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Decode);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Execute);
cpu.tick(&mut bus);
assert_eq!(cpu.phase, Phase::Wait);
assert_eq!(cpu.target, Target::Write(0xBEEF, 0xFF));
cpu.tick(&mut bus);
assert_eq!(cpu.pc, 0x0003);
}
#[test]
fn reset_resets_cpu() {
let mut cpu = Cpu::default();
cpu.regs.set(AB, 0xBEEF);
cpu.pc = 0xC000;
cpu.reset();
assert_eq!(cpu.regs.get(AB), 0x0000, "AB not reset");
assert_eq!(cpu.pc, 0x0000, "PC not reset");
}
}