#![cfg_attr(not(test), no_std)]
#![cfg_attr(feature = "nightly", feature(test))]
#[cfg(feature = "nightly")]
extern crate test;
#[cfg(test)]
mod tests;
mod util;
use util::*;
mod instructions;
/// Trait that systems should implement.
/// Any given function will be called once per cycle, but not all functions
/// will be called every cycle.
pub trait System {
/// Read access
fn read(&mut self, addr: u32, addr_type: AddressType, signals: &Signals) -> u8;
/// Write access
fn write(&mut self, addr: u32, data: u8, addr_type: AddressType, signals: &Signals);
/// IRQ
fn irq(&mut self) -> bool {
false
}
/// NMI
fn nmi(&mut self) -> bool {
false
}
/// RES
fn res(&mut self) -> bool {
false
}
/// RDY
fn rdy(&mut self) -> bool {
true
}
/// Abort
///
/// The Abort pulse active input is used to abort instructions (usually due to an Address Bus condition). A return value of true will inhibit modification of any internal register during the current instruction. Upon completion of this instruction, an interrupt sequence is initiated. The location of the aborted opcode is stored as the return address in stack memory. The Abort vector address is 00FFF8,9 (Emulation mode) or 00FFE8,9 (Native mode). Note that ABORT is a pulse sensitive signal; i.e., an abort will occur whenever true is returned.
fn abort(&mut self) -> bool {
false
}
}
/// Address type of access
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AddressType {
Invalid,
Data,
Program,
Opcode,
Vector,
}
impl AddressType {
pub fn into_vda_vpa(&self) -> (bool, bool) {
match self {
AddressType::Invalid => (false, false),
AddressType::Data => (true, false),
AddressType::Program => (false, true),
AddressType::Opcode => (true, true),
AddressType::Vector => (true, false),
}
}
pub fn into_vda(&self) -> bool {
self.into_vda_vpa().0
}
pub fn into_vpa(&self) -> bool {
self.into_vda_vpa().1
}
pub fn into_vpb(&self) -> bool {
!matches!(self, AddressType::Vector)
}
}
/// CPU Signals
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Signals {
e: bool,
mlb: bool,
m: bool,
x: bool,
rdy: bool,
}
impl Default for Signals {
fn default() -> Self {
Self {
e: true,
mlb: true,
m: true,
x: true,
rdy: true,
}
}
}
impl Signals {
/// Emulation Status (E)
///
/// The Emulation Status output reflects the state of the Emulation (E) mode
/// flag in the Processor Status (P) Register. This signal may be thought of
/// as an opcode extension and used for memory and system management.
pub fn e(&self) -> bool {
self.e
}
/// Memory Lock (MLB)
///
/// The Memory Lock active low output may be used to ensure the integrity of
/// Read Modify Write instructions in a multiprocessor system. Memory Lock
/// indicates the need to defer arbitration of the next bus cycle.
/// Memory Lock is low during the last three or five cycles of ASL, DEC, INC,
/// LSR, ROL, ROR, TRB, and TSB memory referencing instructions, depending on
/// the state of the M flag.
pub fn mlb(&self) -> bool {
self.mlb
}
/// Memory/Index Select Status (MX)
///
/// The Memory/Index Select Status multiplexed output reflects the state of the Accumulator (M) and Index (X) select flags (bits 5 and 4 of the Processor Status (P)
/// Register. Flag M is valid during PHI2 negative transition. and Flag X is
/// valid during PHI2 positive transition. These bits may be thought of as
/// opcode extensions and may be used for memory and system management.
pub fn mx(&self, phi2: bool) -> bool {
if !phi2 {
self.m
} else {
self.x
}
}
pub fn m(&self) -> bool {
self.m
}
pub fn x(&self) -> bool {
self.x
}
/// Ready (RDY)
pub fn rdy(&self) -> bool {
self.rdy
}
}
/// CPU Status Flags
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Flags {
pub carry: bool,
pub decimal: bool,
pub emulation: bool,
pub interrupt_disable: bool,
pub negative: bool,
pub overflow: bool,
pub zero: bool,
pub mem_sel: bool,
pub index_sel: bool,
}
impl Default for Flags {
fn default() -> Self {
Self {
carry: true,
decimal: false,
emulation: true,
interrupt_disable: true,
negative: true,
overflow: false,
zero: true,
mem_sel: true,
index_sel: false,
}
}
}
impl Flags {
const CARRY: u8 = 0b00000001;
const ZERO: u8 = 0b00000010;
const IRQ_DISABLE: u8 = 0b00000100;
const DECIMAL: u8 = 0b00001000;
const INDEX_SEL: u8 = 0b00010000;
const BRK_BIT: u8 = 0b00010000;
const MEM_SEL: u8 = 0b00100000;
const OVERFLOW: u8 = 0b01000000;
const NEGATIVE: u8 = 0b10000000;
/// Returns the flags as an 8-bit byte
pub fn as_byte(self) -> u8 {
let is_native = !self.emulation;
let mut byte = 0u8;
if self.zero {
byte |= Self::ZERO;
}
if self.interrupt_disable {
byte |= Self::IRQ_DISABLE;
}
if self.decimal {
byte |= Self::DECIMAL;
}
if self.overflow {
byte |= Self::OVERFLOW;
}
if self.negative {
byte |= Self::NEGATIVE;
}
if self.carry {
byte |= Self::CARRY;
}
if !is_native || (is_native && self.mem_sel) {
byte |= Self::MEM_SEL;
}
if !is_native {
byte |= Self::BRK_BIT;
} else if self.index_sel {
byte |= Self::INDEX_SEL;
}
byte
}
pub fn set_mask(&mut self, mask: u8, set: bool) {
if mask & Self::CARRY != 0 {
self.carry = set;
}
if mask & Self::ZERO != 0 {
self.zero = set;
}
if mask & Self::IRQ_DISABLE != 0 {
self.interrupt_disable = set;
}
if mask & Self::DECIMAL != 0 {
self.decimal = set;
}
if mask & Self::NEGATIVE != 0 {
self.negative = set;
}
if mask & Self::OVERFLOW != 0 {
self.overflow = set;
}
if !self.emulation {
if mask & Self::MEM_SEL != 0 {
self.mem_sel = set;
}
if mask & Self::INDEX_SEL != 0 {
self.index_sel = set;
}
}
}
pub fn set(&mut self, value: u8) {
self.carry = value & Self::CARRY != 0;
self.zero = value & Self::ZERO != 0;
self.interrupt_disable = value & Self::IRQ_DISABLE != 0;
self.decimal = value & Self::DECIMAL != 0;
self.negative = value & Self::NEGATIVE != 0;
self.overflow = value & Self::OVERFLOW != 0;
if !self.emulation {
self.mem_sel = value & Self::MEM_SEL != 0;
self.index_sel = value & Self::INDEX_SEL != 0;
}
}
}
#[derive(Clone, Debug, Copy, PartialEq, Default)]
pub(crate) enum AddressingMode {
#[default]
Implied,
Accumulator,
Immediate,
Absolute,
AbsoluteIndexedX,
AbsoluteIndexedY,
Direct,
DirectIndirect,
DirectIndirectLong,
DirectIndirectLongIndexedY,
DirectIndexedX,
DirectIndexedY,
DirectIndirectX,
DirectIndirectIndexedY,
Relative,
StackRel,
StackRelIndirectIndexedY,
AbsoluteLong,
AbsoluteLongIndexedX,
IndexedIndirectX,
Indirect,
IndirectLong,
}
impl AddressingMode {
pub fn read(self, cpu: &mut CPU, system: &mut dyn System) -> Option<TaggedByte> {
match self {
AddressingMode::Immediate => match cpu.tcu {
1 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let value = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
Some(TaggedByte::Data(Byte::Low(value)))
}
2 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let value = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
Some(TaggedByte::Data(Byte::High(value)))
}
_ => None,
},
AddressingMode::Implied => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
Some(TaggedByte::Data(Byte::Low(system.read(
effective,
AddressType::Invalid,
&cpu.signals,
))))
}
AddressingMode::Absolute => match cpu.tcu {
1 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let value = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(value);
Some(TaggedByte::Address(Byte::Low(value)))
}
2 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let value = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::High(&mut cpu.temp_addr).set(value);
Some(TaggedByte::Address(Byte::High(value)))
}
3 => {
let effective = ((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32);
let value = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(value)))
}
4 => {
let effective =
(((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32)).wrapping_add(1);
let value = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(value)))
}
_ => None,
},
AddressingMode::Direct => match (
cpu.tcu,
ByteRef::Low(&mut cpu.d).get() == 0,
) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
Some(TaggedByte::Address(Byte::Low(offset)))
}
(2, false) if cpu.d != 0 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc.wrapping_sub(1) as u32);
system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(2, true) | (3, false) => {
let addr = cpu
.d
.wrapping_add(ByteRef::Low(&mut cpu.temp_addr).get() as u16)
as u32;
let value = system.read(addr, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(value)))
}
(3, true) | (4, false) => {
let addr = cpu
.d
.wrapping_add(ByteRef::Low(&mut cpu.temp_addr).get() as u16)
.wrapping_add(1) as u32;
let value = system.read(addr, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(value)))
}
_ => None,
},
AddressingMode::StackRel => match cpu.tcu {
1 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
Some(TaggedByte::Address(Byte::Low(offset)))
}
2 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
3 => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let effective = cpu.s.wrapping_add(offset);
let data = system.read(effective as u32, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(data)))
}
4 => {
let offset = (ByteRef::Low(&mut cpu.temp_addr).get() as u16).wrapping_add(1);
let effective = cpu.s.wrapping_add(offset);
let data = system.read(effective as u32, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(data)))
}
_ => None,
}
AddressingMode::DirectIndexedX => match (
cpu.tcu,
ByteRef::Low(&mut cpu.d).get() == 0,
) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
Some(TaggedByte::Address(Byte::Low(offset)))
}
(2, _) | (3, false) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(3, true) | (4, false) => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get();
let dl_zero = ByteRef::Low(&mut cpu.d).get() == 0;
let effective = if cpu.flags.emulation && dl_zero {
let low = (offset as u16).wrapping_add(cpu.x) & 0xFF;
((cpu.d & 0xFF00) | low) as u32
} else {
cpu.d.wrapping_add(offset as u16).wrapping_add(cpu.x) as u32
};
let value = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(value)))
}
(4, true) | (5, false) => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get();
let dl_zero = ByteRef::Low(&mut cpu.d).get() == 0;
let base = if cpu.flags.emulation && dl_zero {
let low = (offset as u16).wrapping_add(cpu.x) & 0xFF;
(cpu.d & 0xFF00) | low
} else {
cpu.d.wrapping_add(offset as u16).wrapping_add(cpu.x)
};
let effective = base.wrapping_add(1) as u32;
let value = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(value)))
}
_ => None,
}
AddressingMode::AbsoluteIndexedX => {
let add_extra_cycle = (!cpu.flags.emulation && cpu.m16())
|| (cpu.tcu > 2 && (
(cpu.temp_addr & 0xff).wrapping_add(cpu.x & 0xff) > 0xff
));
match (cpu.tcu, add_extra_cycle) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let data = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(data);
None
}
(2, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let data = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::High(&mut cpu.temp_addr).set(data);
None
}
(3, true) => {
let x = ByteRef::Low(&mut cpu.x).get();
let a = cpu.temp_addr as u8;
let a = a.wrapping_add(x);
let a = (cpu.temp_addr & 0xff00) | a as u16;
let effective = ((cpu.dbr as u32) << 16) | (a as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(4, true) | (3, false) => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32))
.wrapping_add(cpu.x as u32);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(data)))
}
(5, true) | (4, false) => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32))
.wrapping_add(cpu.x as u32)
.wrapping_add(1);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(data)))
}
_ => None,
}
}
AddressingMode::AbsoluteIndexedY => {
let add_extra_cycle = (!cpu.flags.emulation && cpu.m16())
|| (cpu.tcu > 2 && (
(cpu.temp_addr & 0xff).wrapping_add(cpu.y & 0xff) > 0xff
));
match (cpu.tcu, add_extra_cycle) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let data = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(data);
None
}
(2, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let data = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::High(&mut cpu.temp_addr).set(data);
None
}
(3, true) => {
let y = ByteRef::Low(&mut cpu.y).get();
let a = cpu.temp_addr as u8;
let a = a.wrapping_add(y);
let a = (cpu.temp_addr & 0xff00) | a as u16;
let effective = ((cpu.dbr as u32) << 16) | (a as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(4, true) | (3, false) => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32))
.wrapping_add(cpu.y as u32);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(data)))
}
(5, true) | (4, false) => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32))
.wrapping_add(cpu.y as u32)
.wrapping_add(1);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(data)))
}
_ => None,
}
}
AddressingMode::DirectIndirect => match (
cpu.tcu,
ByteRef::Low(&mut cpu.d).get() == 0,
) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
None
}
(2, false) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc.wrapping_sub(1) as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(2, true) | (3, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte) as u32;
let aal = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(aal);
None
}
(3, true) | (4, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte).wrapping_add(1) as u32;
let aah = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_addr).set(ByteRef::Low(&mut cpu.temp_data).get());
ByteRef::High(&mut cpu.temp_addr).set(aah);
None
}
(4, true) | (5, false) => {
let effective = ((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(data)))
}
(5, true) | (6, false) => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32)).wrapping_add(1);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(data)))
}
_ => None,
}
AddressingMode::DirectIndirectLong => match (
cpu.tcu,
ByteRef::Low(&mut cpu.d).get() == 0,
) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
None
}
(2, false) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc.wrapping_sub(1) as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(2, true) | (3, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte) as u32;
let aal = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(aal);
None
}
(3, true) | (4, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte).wrapping_add(1) as u32;
let aah = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::High(&mut cpu.temp_data).set(aah);
None
}
(4, true) | (5, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte).wrapping_add(2) as u32;
let aab = system.read(ptr_addr, AddressType::Data, &cpu.signals);
cpu.temp_bank = aab;
cpu.temp_addr = cpu.temp_data;
None
}
(5, true) | (6, false) => {
let effective = ((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(data)))
}
(6, true) | (7, false) => {
let effective = (((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32)).wrapping_add(1);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(data)))
}
_ => None,
}
AddressingMode::DirectIndirectLongIndexedY => match (
cpu.tcu,
ByteRef::Low(&mut cpu.d).get() == 0,
) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
None
}
(2, false) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc.wrapping_sub(1) as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(2, true) | (3, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte) as u32;
let aal = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(aal);
None
}
(3, true) | (4, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte).wrapping_add(1) as u32;
let aah = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::High(&mut cpu.temp_data).set(aah);
None
}
(4, true) | (5, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte).wrapping_add(2) as u32;
let aab = system.read(ptr_addr, AddressType::Data, &cpu.signals);
cpu.temp_bank = aab;
cpu.temp_addr = cpu.temp_data;
None
}
(5, true) | (6, false) => {
let base = ((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32);
let effective = base.wrapping_add(cpu.y as u32);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(data)))
}
(6, true) | (7, false) => {
let base = ((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32);
let effective = base.wrapping_add(cpu.y as u32).wrapping_add(1);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(data)))
}
_ => None,
}
AddressingMode::DirectIndexedY => match (
cpu.tcu,
ByteRef::Low(&mut cpu.d).get() == 0,
) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
Some(TaggedByte::Address(Byte::Low(offset)))
}
(2, _) | (3, false) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(3, true) | (4, false) => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get();
let dl_zero = ByteRef::Low(&mut cpu.d).get() == 0;
let effective = if cpu.flags.emulation && dl_zero {
let low = (offset as u16).wrapping_add(cpu.y) & 0xFF;
((cpu.d & 0xFF00) | low) as u32
} else {
cpu.d.wrapping_add(offset as u16).wrapping_add(cpu.y) as u32
};
let value = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(value)))
}
(4, true) | (5, false) => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get();
let dl_zero = ByteRef::Low(&mut cpu.d).get() == 0;
let base = if cpu.flags.emulation && dl_zero {
let low = (offset as u16).wrapping_add(cpu.y) & 0xFF;
(cpu.d & 0xFF00) | low
} else {
cpu.d.wrapping_add(offset as u16).wrapping_add(cpu.y)
};
let effective = base.wrapping_add(1) as u32;
let value = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(value)))
}
_ => None,
}
AddressingMode::DirectIndirectX => match (
cpu.tcu,
ByteRef::Low(&mut cpu.d).get() == 0,
) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
None
}
(2, false) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc.wrapping_sub(1) as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(2, true) | (3, false) => {
// Always-IO: compute ptr_base = D + DO + X → temp_addr
// In emulation mode with DL=0, (DO + X) wraps within the direct page
// (6502 compatibility). When DL≠0 the full 16-bit sum is used.
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get();
let dl_zero = ByteRef::Low(&mut cpu.d).get() == 0;
let ptr_base = if cpu.flags.emulation && dl_zero {
let low = do_byte.wrapping_add(cpu.x as u8);
(cpu.d & 0xFF00) | (low as u16)
} else {
cpu.d.wrapping_add(do_byte as u16).wrapping_add(cpu.x)
};
cpu.temp_addr = ptr_base;
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(3, true) | (4, false) => {
let aal = system.read(cpu.temp_addr as u32, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(aal);
None
}
(4, true) | (5, false) => {
let aah = system.read(cpu.temp_addr.wrapping_add(1) as u32, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_addr).set(ByteRef::Low(&mut cpu.temp_data).get());
ByteRef::High(&mut cpu.temp_addr).set(aah);
None
}
(5, true) | (6, false) => {
let effective = ((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(data)))
}
(6, true) | (7, false) => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32)).wrapping_add(1);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(data)))
}
_ => None,
}
AddressingMode::DirectIndirectIndexedY => {
let dl_zero = ByteRef::Low(&mut cpu.d).get() == 0;
// Pointer is assembled at tcu=3 (dl_zero) or tcu=4 (dl!=0)
let ptr_ready_at = if dl_zero { 3u8 } else { 4u8 };
let add_page_cycle = (!cpu.flags.emulation && cpu.m16())
|| (cpu.tcu > ptr_ready_at
&& (cpu.temp_addr & 0xff).wrapping_add(cpu.y & 0xff) > 0xff);
match (cpu.tcu, dl_zero, add_page_cycle) {
(1, _, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
None
}
(2, false, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc.wrapping_sub(1) as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(2, true, _) | (3, false, _) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte) as u32;
let aal = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(aal);
None
}
(3, true, _) | (4, false, _) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte).wrapping_add(1) as u32;
let aah = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_addr).set(ByteRef::Low(&mut cpu.temp_data).get());
ByteRef::High(&mut cpu.temp_addr).set(aah);
None
}
(4, true, true) | (5, false, true) => {
// IO: page cross / 16-bit extra cycle
let y = ByteRef::Low(&mut cpu.y).get();
let a = cpu.temp_addr as u8;
let a = a.wrapping_add(y);
let a = (cpu.temp_addr & 0xff00) | a as u16;
let effective = ((cpu.dbr as u32) << 16) | (a as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(4, true, false) | (5, false, false) | (5, true, true) | (6, false, true) => {
// Bank carry from (AA+Y) propagates into DBR
let effective = ((cpu.dbr as u32) << 16)
+ cpu.temp_addr as u32
+ cpu.y as u32;
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(data)))
}
(5, true, false) | (6, false, false) | (6, true, true) | (7, false, true) => {
let effective = ((cpu.dbr as u32) << 16)
+ cpu.temp_addr as u32
+ cpu.y as u32
+ 1;
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(data)))
}
_ => None,
}
}
AddressingMode::StackRelIndirectIndexedY => match cpu.tcu {
1 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
None
}
2 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
3 => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
// Spec: addr=0:S+SO — full S + offset, bank 0, no page-1 restriction
let effective = cpu.s.wrapping_add(offset);
let aal = system.read(effective as u32, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(aal);
None
}
4 => {
let offset = (ByteRef::Low(&mut cpu.temp_addr).get() as u16).wrapping_add(1);
// Spec: addr=0:S+SO+1 — full S + offset+1, bank 0
let effective = cpu.s.wrapping_add(offset);
let aah = system.read(effective as u32, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_addr).set(ByteRef::Low(&mut cpu.temp_data).get());
ByteRef::High(&mut cpu.temp_addr).set(aah);
None
}
5 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
6 => {
// Bank carry from (AA+Y) propagates into DBR
let effective = ((cpu.dbr as u32) << 16)
+ cpu.temp_addr as u32
+ cpu.y as u32;
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(data)))
}
7 => {
let effective = ((cpu.dbr as u32) << 16)
+ cpu.temp_addr as u32
+ cpu.y as u32
+ 1;
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(data)))
}
_ => None,
}
AddressingMode::AbsoluteLong => match cpu.tcu {
1 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let value = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(value);
None
}
2 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let value = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::High(&mut cpu.temp_addr).set(value);
None
}
3 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let value = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
cpu.temp_bank = value;
None
}
4 => {
let effective = ((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(data)))
}
5 => {
let effective = (((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32)).wrapping_add(1);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(data)))
}
_ => None,
}
AddressingMode::AbsoluteLongIndexedX => match cpu.tcu {
1 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let value = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(value);
None
}
2 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let value = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::High(&mut cpu.temp_addr).set(value);
None
}
3 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let value = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
cpu.temp_bank = value;
None
}
4 => {
let base = ((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32);
let effective = base.wrapping_add(cpu.x as u32);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(data)))
}
5 => {
let base = ((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32);
let effective = base.wrapping_add(cpu.x as u32).wrapping_add(1);
let data = system.read(effective, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(data)))
}
_ => None,
}
am => todo!("read {am:?}"),
}
}
pub fn write(
self,
cpu: &mut CPU,
system: &mut dyn System,
mut value: u16,
) -> Option<TaggedByte> {
match self {
AddressingMode::Implied => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
Some(TaggedByte::Data(Byte::Low(system.read(
effective,
AddressType::Invalid,
&cpu.signals,
))))
}
AddressingMode::Absolute => match cpu.tcu {
1 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let value = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(value);
Some(TaggedByte::Address(Byte::Low(value)))
}
2 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let value = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::High(&mut cpu.temp_addr).set(value);
Some(TaggedByte::Address(Byte::High(value)))
}
3 => {
let effective = ((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32);
let value = ByteRef::Low(&mut value).get();
system.write(effective, value, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(value)))
}
4 => {
let effective = ((cpu.dbr as u32) << 16) | (cpu.temp_addr.wrapping_add(1) as u32);
let value = ByteRef::High(&mut value).get();
system.write(effective, value, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(value)))
}
_ => None,
},
AddressingMode::Direct => match (
cpu.tcu,
ByteRef::Low(&mut cpu.d).get() == 0,
) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
Some(TaggedByte::Address(Byte::Low(offset)))
}
(2, false) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc.wrapping_sub(1) as u32);
system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(2, true) | (3, false) => {
let addr = cpu
.d
.wrapping_add(ByteRef::Low(&mut cpu.temp_addr).get() as u16)
as u32;
let value = ByteRef::Low(&mut value).get();
system.write(addr, value, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(value)))
}
(3, true) | (4, false) => {
let addr = cpu
.d
.wrapping_add(ByteRef::Low(&mut cpu.temp_addr).get() as u16)
.wrapping_add(1) as u32;
let value = ByteRef::High(&mut value).get();
system.write(addr, value, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(value)))
}
_ => None,
},
AddressingMode::StackRel => match cpu.tcu {
1 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
Some(TaggedByte::Address(Byte::Low(offset)))
}
2 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
3 => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let effective = cpu.s.wrapping_add(offset);
let value = ByteRef::Low(&mut value).get();
system.write(effective as u32, value, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(value)))
}
4 => {
let offset = (ByteRef::Low(&mut cpu.temp_addr).get() as u16).wrapping_add(1);
let effective = cpu.s.wrapping_add(offset);
let value = ByteRef::High(&mut value).get();
system.write(effective as u32, value, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(value)))
}
_ => None,
}
AddressingMode::AbsoluteLong => match cpu.tcu {
1 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let value = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(value);
Some(TaggedByte::Address(Byte::Low(value)))
}
2 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let v = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::High(&mut cpu.temp_addr).set(v);
Some(TaggedByte::Address(Byte::High(v)))
}
3 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let v = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
cpu.temp_bank = v;
None
}
4 => {
let effective = ((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32);
let v = ByteRef::Low(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(v)))
}
5 => {
let effective = (((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32)).wrapping_add(1);
let v = ByteRef::High(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(v)))
}
_ => None,
},
AddressingMode::AbsoluteLongIndexedX => match cpu.tcu {
1 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let v = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(v);
Some(TaggedByte::Address(Byte::Low(v)))
}
2 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let v = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::High(&mut cpu.temp_addr).set(v);
Some(TaggedByte::Address(Byte::High(v)))
}
3 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let v = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
cpu.temp_bank = v;
None
}
4 => {
let base = ((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32);
let effective = base.wrapping_add(cpu.x as u32);
let v = ByteRef::Low(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(v)))
}
5 => {
let base = ((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32);
let effective = base.wrapping_add(cpu.x as u32).wrapping_add(1);
let v = ByteRef::High(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(v)))
}
_ => None,
},
AddressingMode::AbsoluteIndexedX => {
// Note 4: IO cycle always present for writes
match cpu.tcu {
1 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let data = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(data);
Some(TaggedByte::Address(Byte::Low(data)))
}
2 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let data = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::High(&mut cpu.temp_addr).set(data);
Some(TaggedByte::Address(Byte::High(data)))
}
3 => {
// Always IO for writes
let x = ByteRef::Low(&mut cpu.x).get();
let a = cpu.temp_addr as u8;
let a = a.wrapping_add(x);
let a = (cpu.temp_addr & 0xff00) | a as u16;
let effective = ((cpu.dbr as u32) << 16) | (a as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
4 => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32))
.wrapping_add(cpu.x as u32);
let v = ByteRef::Low(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(v)))
}
5 => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32))
.wrapping_add(cpu.x as u32)
.wrapping_add(1);
let v = ByteRef::High(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(v)))
}
_ => None,
}
}
AddressingMode::AbsoluteIndexedY => {
// Note 4: IO cycle always present for writes
match cpu.tcu {
1 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let data = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(data);
Some(TaggedByte::Address(Byte::Low(data)))
}
2 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let data = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::High(&mut cpu.temp_addr).set(data);
Some(TaggedByte::Address(Byte::High(data)))
}
3 => {
// Always IO for writes
let y = ByteRef::Low(&mut cpu.y).get();
let a = cpu.temp_addr as u8;
let a = a.wrapping_add(y);
let a = (cpu.temp_addr & 0xff00) | a as u16;
let effective = ((cpu.dbr as u32) << 16) | (a as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
4 => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32))
.wrapping_add(cpu.y as u32);
let v = ByteRef::Low(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(v)))
}
5 => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32))
.wrapping_add(cpu.y as u32)
.wrapping_add(1);
let v = ByteRef::High(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(v)))
}
_ => None,
}
}
AddressingMode::DirectIndexedX => match (
cpu.tcu,
ByteRef::Low(&mut cpu.d).get() == 0,
) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
Some(TaggedByte::Address(Byte::Low(offset)))
}
(2, _) | (3, false) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(3, true) | (4, false) => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get();
let dl_zero = ByteRef::Low(&mut cpu.d).get() == 0;
let effective = if cpu.flags.emulation && dl_zero {
let low = (offset as u16).wrapping_add(cpu.x) & 0xFF;
((cpu.d & 0xFF00) | low) as u32
} else {
cpu.d.wrapping_add(offset as u16).wrapping_add(cpu.x) as u32
};
let v = ByteRef::Low(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(v)))
}
(4, true) | (5, false) => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get();
let dl_zero = ByteRef::Low(&mut cpu.d).get() == 0;
let base = if cpu.flags.emulation && dl_zero {
let low = (offset as u16).wrapping_add(cpu.x) & 0xFF;
(cpu.d & 0xFF00) | low
} else {
cpu.d.wrapping_add(offset as u16).wrapping_add(cpu.x)
};
let effective = base.wrapping_add(1) as u32;
let v = ByteRef::High(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(v)))
}
_ => None,
},
AddressingMode::DirectIndexedY => match (
cpu.tcu,
ByteRef::Low(&mut cpu.d).get() == 0,
) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
Some(TaggedByte::Address(Byte::Low(offset)))
}
(2, _) | (3, false) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(3, true) | (4, false) => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get();
let dl_zero = ByteRef::Low(&mut cpu.d).get() == 0;
let effective = if cpu.flags.emulation && dl_zero {
let low = (offset as u16).wrapping_add(cpu.y) & 0xFF;
((cpu.d & 0xFF00) | low) as u32
} else {
cpu.d.wrapping_add(offset as u16).wrapping_add(cpu.y) as u32
};
let v = ByteRef::Low(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(v)))
}
(4, true) | (5, false) => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get();
let dl_zero = ByteRef::Low(&mut cpu.d).get() == 0;
let base = if cpu.flags.emulation && dl_zero {
let low = (offset as u16).wrapping_add(cpu.y) & 0xFF;
(cpu.d & 0xFF00) | low
} else {
cpu.d.wrapping_add(offset as u16).wrapping_add(cpu.y)
};
let effective = base.wrapping_add(1) as u32;
let v = ByteRef::High(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(v)))
}
_ => None,
},
AddressingMode::DirectIndirect => match (
cpu.tcu,
ByteRef::Low(&mut cpu.d).get() == 0,
) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
None
}
(2, false) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc.wrapping_sub(1) as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(2, true) | (3, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte) as u32;
let aal = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(aal);
None
}
(3, true) | (4, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte).wrapping_add(1) as u32;
let aah = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_addr).set(ByteRef::Low(&mut cpu.temp_data).get());
ByteRef::High(&mut cpu.temp_addr).set(aah);
None
}
(4, true) | (5, false) => {
let effective = ((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32);
let v = ByteRef::Low(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(v)))
}
(5, true) | (6, false) => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32)).wrapping_add(1);
let v = ByteRef::High(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(v)))
}
_ => None,
},
AddressingMode::DirectIndirectLong => match (
cpu.tcu,
ByteRef::Low(&mut cpu.d).get() == 0,
) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
None
}
(2, false) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc.wrapping_sub(1) as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(2, true) | (3, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte) as u32;
let aal = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(aal);
None
}
(3, true) | (4, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte).wrapping_add(1) as u32;
let aah = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::High(&mut cpu.temp_data).set(aah);
None
}
(4, true) | (5, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte).wrapping_add(2) as u32;
let aab = system.read(ptr_addr, AddressType::Data, &cpu.signals);
cpu.temp_bank = aab;
cpu.temp_addr = cpu.temp_data;
None
}
(5, true) | (6, false) => {
let effective = ((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32);
let v = ByteRef::Low(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(v)))
}
(6, true) | (7, false) => {
let effective = (((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32)).wrapping_add(1);
let v = ByteRef::High(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(v)))
}
_ => None,
},
AddressingMode::DirectIndirectLongIndexedY => match (
cpu.tcu,
ByteRef::Low(&mut cpu.d).get() == 0,
) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
None
}
(2, false) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc.wrapping_sub(1) as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(2, true) | (3, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte) as u32;
let aal = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(aal);
None
}
(3, true) | (4, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte).wrapping_add(1) as u32;
let aah = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::High(&mut cpu.temp_data).set(aah);
None
}
(4, true) | (5, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte).wrapping_add(2) as u32;
let aab = system.read(ptr_addr, AddressType::Data, &cpu.signals);
cpu.temp_bank = aab;
cpu.temp_addr = cpu.temp_data;
None
}
(5, true) | (6, false) => {
let base = ((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32);
let effective = base.wrapping_add(cpu.y as u32);
let v = ByteRef::Low(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(v)))
}
(6, true) | (7, false) => {
let base = ((cpu.temp_bank as u32) << 16) | (cpu.temp_addr as u32);
let effective = base.wrapping_add(cpu.y as u32).wrapping_add(1);
let v = ByteRef::High(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(v)))
}
_ => None,
},
AddressingMode::DirectIndirectX => match (
cpu.tcu,
ByteRef::Low(&mut cpu.d).get() == 0,
) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
None
}
(2, false) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc.wrapping_sub(1) as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(2, true) | (3, false) => {
// Always-IO: compute ptr_base = D + DO + X → temp_addr
// In emulation mode with DL=0, (DO + X) wraps within the direct page
// (6502 compatibility). When DL≠0 the full 16-bit sum is used.
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get();
let dl_zero = ByteRef::Low(&mut cpu.d).get() == 0;
let ptr_base = if cpu.flags.emulation && dl_zero {
let low = do_byte.wrapping_add(cpu.x as u8);
(cpu.d & 0xFF00) | (low as u16)
} else {
cpu.d.wrapping_add(do_byte as u16).wrapping_add(cpu.x)
};
cpu.temp_addr = ptr_base;
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(3, true) | (4, false) => {
let aal = system.read(cpu.temp_addr as u32, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(aal);
None
}
(4, true) | (5, false) => {
let aah = system.read(cpu.temp_addr.wrapping_add(1) as u32, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_addr).set(ByteRef::Low(&mut cpu.temp_data).get());
ByteRef::High(&mut cpu.temp_addr).set(aah);
None
}
(5, true) | (6, false) => {
let effective = ((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32);
let v = ByteRef::Low(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(v)))
}
(6, true) | (7, false) => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32)).wrapping_add(1);
let v = ByteRef::High(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(v)))
}
_ => None,
},
AddressingMode::DirectIndirectIndexedY => {
let dl_zero = ByteRef::Low(&mut cpu.d).get() == 0;
// Note 4: IO cycle always present for writes (no page-cross check)
match (cpu.tcu, dl_zero) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
None
}
(2, false) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc.wrapping_sub(1) as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(2, true) | (3, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte) as u32;
let aal = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(aal);
None
}
(3, true) | (4, false) => {
let do_byte = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
let ptr_addr = cpu.d.wrapping_add(do_byte).wrapping_add(1) as u32;
let aah = system.read(ptr_addr, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_addr).set(ByteRef::Low(&mut cpu.temp_data).get());
ByteRef::High(&mut cpu.temp_addr).set(aah);
None
}
(4, true) | (5, false) => {
// IO cycle always present for writes (Note 4)
let y = ByteRef::Low(&mut cpu.y).get();
let a = cpu.temp_addr as u8;
let a = a.wrapping_add(y);
let a = (cpu.temp_addr & 0xff00) | a as u16;
let effective = ((cpu.dbr as u32) << 16) | (a as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(5, true) | (6, false) => {
let effective = ((cpu.dbr as u32) << 16)
+ cpu.temp_addr as u32
+ cpu.y as u32;
let v = ByteRef::Low(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(v)))
}
(6, true) | (7, false) => {
let effective = ((cpu.dbr as u32) << 16)
+ cpu.temp_addr as u32
+ cpu.y as u32
+ 1;
let v = ByteRef::High(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(v)))
}
_ => None,
}
}
AddressingMode::StackRelIndirectIndexedY => match cpu.tcu {
1 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = system.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
None
}
2 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
3 => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get() as u16;
// Spec: addr=0:S+SO — full S + offset, bank 0
let effective = cpu.s.wrapping_add(offset);
let aal = system.read(effective as u32, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(aal);
None
}
4 => {
let offset = (ByteRef::Low(&mut cpu.temp_addr).get() as u16).wrapping_add(1);
// Spec: addr=0:S+SO+1 — full S + offset+1, bank 0
let effective = cpu.s.wrapping_add(offset);
let aah = system.read(effective as u32, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_addr).set(ByteRef::Low(&mut cpu.temp_data).get());
ByteRef::High(&mut cpu.temp_addr).set(aah);
None
}
5 => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let _ = system.read(effective, AddressType::Invalid, &cpu.signals);
None
}
6 => {
let effective = ((cpu.dbr as u32) << 16)
+ cpu.temp_addr as u32
+ cpu.y as u32;
let v = ByteRef::Low(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(v)))
}
7 => {
let effective = ((cpu.dbr as u32) << 16)
+ cpu.temp_addr as u32
+ cpu.y as u32
+ 1;
let v = ByteRef::High(&mut value).get();
system.write(effective, v, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(v)))
}
_ => None,
},
AddressingMode::Implied | AddressingMode::Immediate => None,
am => todo!("write {am:?}"),
}
}
pub fn rwb<F: Fn(&mut CPU, u16, bool) -> u16>(self, cpu: &mut CPU, sys: &mut dyn System, f: F, b16: bool) -> Option<TaggedByte> {
match (self, cpu.tcu, b16) {
(AddressingMode::Accumulator, 1, _) => {
let a = f(cpu, cpu.a, cpu.a16());
if !cpu.aborted {
if cpu.a16() {
cpu.a = a;
} else {
ByteRef::Low(&mut cpu.a).set(a as u8);
}
}
Some(TaggedByte::Data(Byte::Low(a as u8)))
}
(AddressingMode::Accumulator, _, _) => None,
(AddressingMode::Absolute, 1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let value = sys.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(value);
Some(TaggedByte::Address(Byte::Low(value)))
}
(AddressingMode::Absolute, 2, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let value = sys.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::High(&mut cpu.temp_addr).set(value);
Some(TaggedByte::Address(Byte::High(value)))
}
(AddressingMode::Absolute, 3, _) => {
cpu.signals.mlb = false;
let effective = ((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32);
let value = sys.read(effective, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(value);
None
}
(AddressingMode::Absolute, 4, true) => {
let effective = ((cpu.dbr as u32) << 16) | (cpu.temp_addr.wrapping_add(1) as u32);
let value = sys.read(effective, AddressType::Data, &cpu.signals);
ByteRef::High(&mut cpu.temp_data).set(value);
None
}
(AddressingMode::Absolute, 5, true) | (AddressingMode::Absolute, 4, false) => {
let effective = ((cpu.dbr as u32) << 16) | (cpu.temp_addr.wrapping_add(1) as u32);
if cpu.flags.emulation {
let data = ByteRef::High(&mut cpu.temp_data).get();
sys.write(effective, data, AddressType::Invalid, &cpu.signals);
} else {
let _ = sys.read(effective, AddressType::Invalid, &cpu.signals);
}
let data = f(cpu, cpu.temp_data, b16);
if !cpu.aborted {
cpu.temp_data = data;
}
None
}
(AddressingMode::Absolute, 6, true) => {
let effective = ((cpu.dbr as u32) << 16) | (cpu.temp_addr.wrapping_add(1) as u32);
let data = ByteRef::High(&mut cpu.temp_data).get();
sys.write(effective, data, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(data)))
}
(AddressingMode::Absolute, 7, true) | (AddressingMode::Absolute, 5, false) => {
let effective = ((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32);
let data = ByteRef::Low(&mut cpu.temp_data).get();
sys.write(effective, data, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(data)))
}
// Direct R-M-W (§10b): 5 cycles base +1 DL≠0 +2 16-bit
(AddressingMode::Direct, _, _) => {
let dl0 = ByteRef::Low(&mut cpu.d).get() == 0;
match (cpu.tcu, dl0, b16) {
(1, _, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = sys.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
Some(TaggedByte::Address(Byte::Low(offset)))
}
(2, false, _) => {
// DL≠0 penalty IO at previous PC
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc.wrapping_sub(1) as u32);
sys.read(effective, AddressType::Invalid, &cpu.signals);
None
}
// Read data low: tcu=2 for DL=0, tcu=3 for DL≠0
(2, true, _) | (3, false, _) => {
let addr = cpu.d.wrapping_add(ByteRef::Low(&mut cpu.temp_addr).get() as u16) as u32;
let value = sys.read(addr, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(value);
None
}
// Read data high (16-bit): tcu=3 for DL=0, tcu=4 for DL≠0
(3, true, true) | (4, false, true) => {
let addr = cpu.d.wrapping_add(ByteRef::Low(&mut cpu.temp_addr).get() as u16).wrapping_add(1) as u32;
let value = sys.read(addr, AddressType::Data, &cpu.signals);
ByteRef::High(&mut cpu.temp_data).set(value);
None
}
// IO + apply: 8-bit tcu=3(DL=0)/4(DL≠0); 16-bit tcu=4(DL=0)/5(DL≠0)
(3, true, false) | (4, false, false) | (4, true, true) | (5, false, true) => {
let addr = cpu.d.wrapping_add(ByteRef::Low(&mut cpu.temp_addr).get() as u16) as u32;
if cpu.flags.emulation {
let data = ByteRef::High(&mut cpu.temp_data).get();
sys.write(addr.wrapping_add(1), data, AddressType::Invalid, &cpu.signals);
} else {
let _ = sys.read(addr.wrapping_add(1), AddressType::Invalid, &cpu.signals);
}
let data = f(cpu, cpu.temp_data, b16);
if !cpu.aborted {
cpu.temp_data = data;
}
None
}
// Write high byte (16-bit): tcu=5(DL=0)/6(DL≠0)
(5, true, true) | (6, false, true) => {
let addr = cpu.d.wrapping_add(ByteRef::Low(&mut cpu.temp_addr).get() as u16).wrapping_add(1) as u32;
let data = ByteRef::High(&mut cpu.temp_data).get();
sys.write(addr, data, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(data)))
}
// Write low byte (final): 8-bit tcu=4(DL=0)/5(DL≠0); 16-bit tcu=6(DL=0)/7(DL≠0)
(4, true, false) | (5, false, false) | (6, true, true) | (7, false, true) => {
let addr = cpu.d.wrapping_add(ByteRef::Low(&mut cpu.temp_addr).get() as u16) as u32;
let data = ByteRef::Low(&mut cpu.temp_data).get();
sys.write(addr, data, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(data)))
}
_ => None,
}
}
// Direct Indexed with X R-M-W (§16b): 6 cycles base +1 DL≠0 +2 16-bit
(AddressingMode::DirectIndexedX, _, _) => {
let dl0 = ByteRef::Low(&mut cpu.d).get() == 0;
match (cpu.tcu, dl0, b16) {
(1, _, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let offset = sys.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(offset);
Some(TaggedByte::Address(Byte::Low(offset)))
}
(2, false, _) => {
// DL≠0 penalty IO
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc.wrapping_sub(1) as u32);
sys.read(effective, AddressType::Invalid, &cpu.signals);
None
}
// X-addition IO (mandatory): tcu=2(DL=0)/3(DL≠0)
(2, true, _) | (3, false, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc.wrapping_sub(1) as u32);
sys.read(effective, AddressType::Invalid, &cpu.signals);
None
}
// Read data low: tcu=3(DL=0)/4(DL≠0)
(3, true, _) | (4, false, _) => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get();
let addr = if cpu.flags.emulation && dl0 {
((cpu.d & 0xFF00) | ((offset as u16).wrapping_add(cpu.x) & 0xFF)) as u32
} else {
cpu.d.wrapping_add(offset as u16).wrapping_add(cpu.x) as u32
};
let value = sys.read(addr, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(value);
None
}
// Read data high (16-bit): tcu=4(DL=0)/5(DL≠0)
(4, true, true) | (5, false, true) => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get();
let addr = cpu.d.wrapping_add(offset as u16).wrapping_add(cpu.x).wrapping_add(1) as u32;
let value = sys.read(addr, AddressType::Data, &cpu.signals);
ByteRef::High(&mut cpu.temp_data).set(value);
None
}
// IO + apply: 8-bit tcu=4(DL=0)/5(DL≠0); 16-bit tcu=5(DL=0)/6(DL≠0)
(4, true, false) | (5, false, false) | (5, true, true) | (6, false, true) => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get();
let addr = if cpu.flags.emulation && dl0 {
((cpu.d & 0xFF00) | ((offset as u16).wrapping_add(cpu.x) & 0xFF)) as u32
} else {
cpu.d.wrapping_add(offset as u16).wrapping_add(cpu.x) as u32
};
if cpu.flags.emulation {
let data = ByteRef::Low(&mut cpu.temp_data).get();
sys.write(addr, data, AddressType::Invalid, &cpu.signals);
} else {
let _ = sys.read(addr.wrapping_add(1), AddressType::Invalid, &cpu.signals);
}
let data = f(cpu, cpu.temp_data, b16);
if !cpu.aborted {
cpu.temp_data = data;
}
None
}
// Write high byte (16-bit): tcu=6(DL=0)/7(DL≠0)
(6, true, true) | (7, false, true) => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get();
let addr = cpu.d.wrapping_add(offset as u16).wrapping_add(cpu.x).wrapping_add(1) as u32;
let data = ByteRef::High(&mut cpu.temp_data).get();
sys.write(addr, data, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(data)))
}
// Write low byte (final): 8-bit tcu=5(DL=0)/6(DL≠0); 16-bit tcu=7(DL=0)/8(DL≠0)
(5, true, false) | (6, false, false) | (7, true, true) | (8, false, true) => {
let offset = ByteRef::Low(&mut cpu.temp_addr).get();
let addr = if cpu.flags.emulation && dl0 {
((cpu.d & 0xFF00) | ((offset as u16).wrapping_add(cpu.x) & 0xFF)) as u32
} else {
cpu.d.wrapping_add(offset as u16).wrapping_add(cpu.x) as u32
};
let data = ByteRef::Low(&mut cpu.temp_data).get();
sys.write(addr, data, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(data)))
}
_ => None,
}
}
// Absolute Indexed with X R-M-W (§6b): 7 cycles base +2 16-bit (IO always present)
(AddressingMode::AbsoluteIndexedX, _, _) => {
match (cpu.tcu, b16) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let data = sys.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::Low(&mut cpu.temp_addr).set(data);
None
}
(2, _) => {
let effective = ((cpu.pbr as u32) << 16) | (cpu.pc as u32);
let data = sys.read(effective, AddressType::Program, &cpu.signals);
cpu.pc = cpu.pc.wrapping_add(1);
ByteRef::High(&mut cpu.temp_addr).set(data);
None
}
(3, _) => {
// Mandatory IO for X-add (always present for RMW)
let xl = ByteRef::Low(&mut cpu.x).get();
let al = cpu.temp_addr as u8;
let preliminary = (cpu.temp_addr & 0xff00) | (al.wrapping_add(xl) as u16);
let effective = ((cpu.dbr as u32) << 16) | (preliminary as u32);
let _ = sys.read(effective, AddressType::Invalid, &cpu.signals);
None
}
(4, _) => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32))
.wrapping_add(cpu.x as u32);
let value = sys.read(effective, AddressType::Data, &cpu.signals);
ByteRef::Low(&mut cpu.temp_data).set(value);
None
}
(5, true) => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32))
.wrapping_add(cpu.x as u32)
.wrapping_add(1);
let value = sys.read(effective, AddressType::Data, &cpu.signals);
ByteRef::High(&mut cpu.temp_data).set(value);
None
}
(5, false) | (6, true) => {
// IO + apply
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32))
.wrapping_add(cpu.x as u32)
.wrapping_add(1);
if cpu.flags.emulation {
let data = ByteRef::High(&mut cpu.temp_data).get();
sys.write(effective, data, AddressType::Invalid, &cpu.signals);
} else {
let _ = sys.read(effective, AddressType::Invalid, &cpu.signals);
}
let data = f(cpu, cpu.temp_data, b16);
if !cpu.aborted {
cpu.temp_data = data;
}
None
}
(7, true) => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32))
.wrapping_add(cpu.x as u32)
.wrapping_add(1);
let data = ByteRef::High(&mut cpu.temp_data).get();
sys.write(effective, data, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::High(data)))
}
(6, false) | (8, true) => {
let effective = (((cpu.dbr as u32) << 16) | (cpu.temp_addr as u32))
.wrapping_add(cpu.x as u32);
let data = ByteRef::Low(&mut cpu.temp_data).get();
sys.write(effective, data, AddressType::Data, &cpu.signals);
Some(TaggedByte::Data(Byte::Low(data)))
}
_ => None,
}
}
_ => None,
}
}
}
/// List of conditions
#[derive(Clone, Debug, Copy, PartialEq, Default)]
pub(crate) enum Condition {
#[default]
Always,
Carry(bool),
Equal(bool),
Minus(bool),
Overflow(bool),
}
/// Internal state machine
#[derive(Clone, Debug, Copy, PartialEq, Default)]
pub(crate) enum State {
#[default]
Fetch,
Interrupt {
vector: u16,
set_brk: bool,
},
Reset,
Abort,
Brk,
Cop,
// Instructions
Instruction(instructions::InstructionFn, AddressingMode),
}
#[derive(Clone, Copy, Debug, Default)]
struct Backup {
a: u16,
x: u16,
y: u16,
pc: u16,
s: u16,
d: u16,
flags: Flags,
pbr: u8,
dbr: u8,
wai: bool,
stp: bool,
}
impl From<&CPU> for Backup {
fn from(c: &CPU) -> Backup {
Backup {
a: c.a,
x: c.x,
y: c.y,
pc: c.pc,
s: c.s,
d: c.d,
flags: c.flags,
pbr: c.pbr,
dbr: c.dbr,
wai: c.wai,
stp: c.stp,
}
}
}
impl Backup {
fn restore(self, c: &mut CPU) {
*c = CPU {
a: self.a,
x: self.x,
y: self.y,
pc: self.pc,
s: self.s,
d: self.d,
flags: self.flags,
pbr: self.pbr,
dbr: self.dbr,
wai: self.wai,
stp: self.stp,
..*c
};
}
}
/// 65c816
#[derive(Clone, Debug)]
pub struct CPU {
/// Instruction Register
ir: u8,
/// Timing Control Unit
tcu: u8,
/// Accumulator
a: u16,
/// Data Bank Register
dbr: u8,
/// Direct
d: u16,
/// X Index Register
x: u16,
/// Y Index Register
y: u16,
/// Program Bank Register
pbr: u8,
/// Program Counter
pc: u16,
/// Stack Pointer
s: u16,
/// RDY signal internal
rdy: bool,
/// Wait for interrupt (WAI) status
wai: bool,
/// Stop status
stp: bool,
/// Flags
flags: Flags,
/// Signals
signals: Signals,
/// CPU State
state: State,
/// Whether to inhibit internal register changes
aborted: bool,
/// Temporary scratch register for internal use.
/// Used for addressing instructions
temp_addr: u16,
/// Temporary scratch register for internal use.
/// Used for addressing instructions
temp_data: u16,
/// Tempoary scratch register for internal use.
/// Used for storing a bank address
temp_bank: u8,
/// Last NMI (for edge triggered)
last_nmi: bool,
/// Backup regs for abort
backup: Backup,
}
impl Default for CPU {
fn default() -> Self {
Self {
ir: 0xff,
tcu: 0xff,
a: 0xff,
dbr: 0xff,
d: 0xffff,
x: 0xffff,
y: 0xffff,
pbr: 0xff,
pc: 0xffff,
s: 0xffff,
rdy: true,
wai: false,
stp: true,
aborted: false,
flags: Flags::default(),
signals: Signals::default(),
state: State::default(),
temp_addr: 0,
temp_data: 0,
temp_bank: 0,
last_nmi: false,
backup: Backup::default(),
}
}
}
impl CPU {
#[inline(always)]
pub fn new() -> Self {
Self::default()
}
pub fn cycle(&mut self, system: &mut dyn System) {
let (rdy, res, nmi, irq, abort) = (
self.rdy(system),
system.res(),
system.nmi(),
system.irq(),
system.abort(),
);
if !rdy && !self.wai && !res {
return;
}
if self.stp && !res {
return;
}
if self.wai && !(res || nmi || irq || abort) {
return;
}
// Hijack the state for ABORTs
if self.aborted && self.state == State::Fetch {
self.backup.restore(self);
self.state = State::Abort;
} else if abort {
self.aborted = true;
}
self.tcu = self.tcu.wrapping_add(1);
self.signals.rdy = rdy;
fn implied(cpu: &mut CPU, system: &mut dyn System) {
let _ = AddressingMode::Implied.read(cpu, system);
cpu.state = State::Fetch;
}
fn interrupt(
cpu: &mut CPU,
system: &mut dyn System,
vector: u16,
store_pc_p: bool,
set_b: bool,
) {
match (cpu.tcu, cpu.flags.emulation) {
(1, _) => {
let effective = ((cpu.pbr as u32) << 16) | cpu.pc as u32;
system.read(effective, AddressType::Invalid, &cpu.signals);
}
(2, false) => cpu.stack_push(system, cpu.pbr, !store_pc_p),
(2, true) | (3, false) => {
let value = ByteRef::High(&mut cpu.pc).get();
cpu.stack_push(system, value, !store_pc_p);
}
(3, true) | (4, false) => {
let value = ByteRef::Low(&mut cpu.pc).get();
cpu.stack_push(system, value, !store_pc_p);
}
(4, true) | (5, false) => {
let value = cpu.flags.as_byte()
& if cpu.flags.emulation && !set_b {
!Flags::BRK_BIT
} else {
0xff
};
cpu.stack_push(system, value, !store_pc_p);
}
(5, true) | (6, false) => {
cpu.flags.interrupt_disable = true;
ByteRef::Low(&mut cpu.pc).set(system.read(
vector as u32,
AddressType::Vector,
&cpu.signals,
));
}
(6, true) | (7, false) => {
ByteRef::High(&mut cpu.pc).set(system.read(
vector.wrapping_add(1) as u32,
AddressType::Vector,
&cpu.signals,
));
cpu.pbr = 0;
cpu.state = State::Fetch;
}
_ => unreachable!(),
}
}
match self.state {
State::Reset => {
if res {
self.tcu = 0;
return;
}
match self.tcu {
0..=2 => {
self.stp = false;
self.wai = false;
self.flags.emulation = true;
self.flags.mem_sel = true;
self.flags.index_sel = true;
self.flags.decimal = false;
self.signals.m = true;
self.signals.x = true;
self.aborted = false;
self.d = 0;
self.dbr = 0;
self.pbr = 0;
self.s = 0x0100;
ByteRef::High(&mut self.x).set(0x00);
ByteRef::High(&mut self.y).set(0x00);
let _ = system.read(0x0000ff, AddressType::Invalid, &self.signals);
}
4 => {
self.s = 0x01ff;
interrupt(self, system, 0xfffc, false, false);
}
_ => interrupt(self, system, 0xfffc, false, false),
}
}
State::Abort => {
if self.flags.emulation {
interrupt(self, system, 0x00fff8, true, false);
} else {
interrupt(self, system, 0x00ffe8, true, false);
}
}
State::Interrupt { vector, set_brk } => interrupt(self, system, vector, true, set_brk),
State::Brk => {
let vector = if self.flags.emulation { 0xfffe } else { 0xffe6 };
match (self.tcu, self.flags.emulation) {
(1, _) => {
let effective = ((self.pbr as u32) << 16) | self.pc as u32;
system.read(effective, AddressType::Program, &self.signals);
self.pc = self.pc.wrapping_add(1);
}
(5, true) | (6, false) => {
self.flags.decimal = false;
self.flags.interrupt_disable = true;
interrupt(self, system, vector, true, true);
}
(7, false) => {
let dbr = self.dbr;
interrupt(self, system, vector, true, true);
self.dbr = dbr;
}
_ => interrupt(self, system, vector, true, true),
}
}
State::Cop => {
let vector = if self.flags.emulation { 0xfff4 } else { 0xffe4 };
match (self.tcu, self.flags.emulation) {
(1, _) => {
let effective = ((self.pbr as u32) << 16) | self.pc as u32;
system.read(effective, AddressType::Program, &self.signals);
self.pc = self.pc.wrapping_add(1);
}
(5, true) | (6, false) => {
self.flags.decimal = false;
self.flags.interrupt_disable = true;
interrupt(self, system, vector, true, true);
}
(7, false) => {
let dbr = self.dbr;
interrupt(self, system, vector, true, true);
self.dbr = dbr;
}
_ => interrupt(self, system, vector, true, true),
}
}
State::Fetch => {
self.signals.mlb = true;
self.tcu = 0;
if self.flags.emulation {
ByteRef::High(&mut self.s).set(0x01);
}
let effective = ((self.pbr as u32) << 16) | (self.pc as u32);
self.ir = system.read(effective, AddressType::Opcode, &self.signals);
self.backup = Backup::from(&*self);
if res {
self.ir = 0x00;
self.stp = false;
self.wai = false;
self.state = State::Reset;
return;
} else if nmi && !self.last_nmi {
self.last_nmi = true;
self.ir = 0x00;
self.wai = false;
self.state = State::Interrupt {
vector: if self.flags.emulation { 0xfffa } else { 0xffea },
set_brk: false,
};
return;
} else {
self.last_nmi = nmi;
if irq {
let had_wai = self.wai;
self.wai = false;
if !had_wai || !self.flags.interrupt_disable {
self.ir = 0x00;
self.state = State::Interrupt {
vector: if self.flags.emulation { 0xfffe } else { 0xffee },
set_brk: false,
};
return;
}
}
}
self.pc = self.pc.wrapping_add(1);
self.state = match self.ir {
0x00 => State::Brk,
0x02 => State::Cop,
ir => {
let (f, am) = instructions::INSTRUCTIONS[ir as usize];
State::Instruction(f, am)
}
}
}
State::Instruction(f, am) => {
f(self, system, am);
// Enforce emulation-mode stack page after instruction completes
if self.state == State::Fetch && self.flags.emulation {
ByteRef::High(&mut self.s).set(0x01);
}
}
_ => todo!("{:?}", self.state),
}
}
/// RDY signal, with internal pulldown.
#[inline(always)]
pub fn rdy(&self, system: &mut dyn System) -> bool {
system.rdy() & self.rdy
}
/// Signals
#[inline(always)]
pub fn signals(&self) -> &Signals {
&self.signals
}
/// Flags
#[inline(always)]
pub fn flags(&self) -> &Flags {
&self.flags
}
/// Processor stopped status
#[inline(always)]
pub fn stopped(&self) -> bool {
self.stp
}
/// Returns the width of the accumulator, either 8 or 16
#[inline(always)]
fn a_width(&self) -> u8 {
match (self.flags.emulation, self.flags.mem_sel) {
(true, _) | (false, true) => 8,
(false, false) => 16,
}
}
#[inline(always)]
fn a8(&self) -> bool {
self.a_width() == 8
}
#[inline(always)]
fn a16(&self) -> bool {
self.a_width() == 16
}
/// Returns the width of index registers, either 8 or 16
#[inline(always)]
fn index_width(&self) -> u8 {
match (self.flags.emulation, self.flags.index_sel) {
(true, _) | (false, true) => 8,
(false, false) => 16,
}
}
#[inline(always)]
fn m8(&self) -> bool {
self.index_width() == 8
}
#[inline(always)]
fn m16(&self) -> bool {
self.index_width() == 16
}
/// Push to stack
fn stack_push(&mut self, system: &mut dyn System, byte: u8, as_read: bool) {
if as_read {
system.read(self.s as u32, AddressType::Data, &self.signals);
} else {
system.write(self.s as u32, byte, AddressType::Data, &self.signals);
}
if !self.aborted {
self.s = self.s.wrapping_sub(1);
if self.flags.emulation {
ByteRef::High(&mut self.s).set(0x01);
}
}
}
/// Pop from stack
fn stack_pop(&mut self, system: &mut dyn System) -> u8 {
if !self.aborted {
self.s = self.s.wrapping_add(1);
if self.flags.emulation {
ByteRef::High(&mut self.s).set(0x01);
}
}
system.read(self.s as u32, AddressType::Data, &self.signals)
}
/// Push without mid-instruction emulation page enforcement (for 65816-native instructions).
/// The caller is responsible for enforcing emulation page 1 after instruction completion.
fn stack_push_raw(&mut self, system: &mut dyn System, byte: u8, as_read: bool) {
if as_read {
system.read(self.s as u32, AddressType::Data, &self.signals);
} else {
system.write(self.s as u32, byte, AddressType::Data, &self.signals);
}
if !self.aborted {
self.s = self.s.wrapping_sub(1);
}
}
/// Pop without mid-instruction emulation page enforcement (for 65816-native instructions).
/// The caller is responsible for enforcing emulation page 1 after instruction completion.
fn stack_pop_raw(&mut self, system: &mut dyn System) -> u8 {
if !self.aborted {
self.s = self.s.wrapping_add(1);
}
system.read(self.s as u32, AddressType::Data, &self.signals)
}
/// Enforce emulation mode page 1 stack constraint.
/// Called after 65816-native instructions complete in emulation mode.
fn enforce_emulation_stack(&mut self) {
if self.flags.emulation {
ByteRef::High(&mut self.s).set(0x01);
}
}
/// Sets the emulation flag state
pub fn set_e(&mut self, e: bool) {
self.flags.emulation = e;
self.signals.e = e;
if e {
self.flags.mem_sel = true;
self.flags.index_sel = true;
self.signals.m = true;
self.signals.x = true;
ByteRef::High(&mut self.x).set(0);
ByteRef::High(&mut self.y).set(0);
ByteRef::High(&mut self.s).set(0x01);
}
}
/// Sets the program bank register
#[inline(always)]
pub fn set_pbr(&mut self, pbr: u8) {
self.pbr = pbr;
}
/// Sets the program bank register
#[inline(always)]
pub fn set_dbr(&mut self, dbr: u8) {
self.dbr = dbr;
}
/// Set the program counter
#[inline(always)]
pub fn set_pc(&mut self, pc: u16) {
self.pc = pc;
}
/// Set the stack pointer
#[inline(always)]
pub fn set_s(&mut self, s: u16) {
self.s = s;
if self.flags.emulation {
ByteRef::High(&mut self.s).set(0x01);
}
}
/// Sets the flag register
#[inline(always)]
pub fn set_p(&mut self, p: u8) {
self.flags.set(p);
self.signals.m = self.flags.mem_sel;
self.signals.x = self.flags.index_sel;
if self.m8() {
ByteRef::High(&mut self.x).set(0);
ByteRef::High(&mut self.y).set(0);
}
}
#[inline(always)]
pub fn set_acc(&mut self, mut acc: u16) {
if self.a_width() == 8 {
ByteRef::Low(&mut self.a).set(ByteRef::Low(&mut acc).get());
ByteRef::High(&mut self.a).set(0);
} else {
self.a = acc;
}
}
/// Sets the accumulator's low byte
#[inline(always)]
pub fn set_a(&mut self, a: u8) {
ByteRef::Low(&mut self.a).set(a);
}
/// Sets the accumulator's high byte
#[inline(always)]
pub fn set_b(&mut self, b: u8) {
ByteRef::High(&mut self.a).set(b);
}
/// Sets the accumulator's entire word
#[inline(always)]
pub fn set_c(&mut self, c: u16) {
self.a = c;
}
/// Sets the X index register
pub fn set_x(&mut self, mut x: u16) {
if self.m8() {
ByteRef::Low(&mut self.x).set(ByteRef::Low(&mut x).get());
ByteRef::High(&mut self.x).set(0);
} else {
self.x = x;
}
}
/// Sets the Y index register
pub fn set_y(&mut self, mut y: u16) {
if self.m8() {
ByteRef::Low(&mut self.y).set(ByteRef::Low(&mut y).get());
ByteRef::High(&mut self.y).set(0);
} else {
self.y = y;
}
}
/// Sets the Direct register (D)
#[inline(always)]
pub fn set_d(&mut self, d: u16) {
self.d = d;
}
/// Returns the IR
#[inline(always)]
pub fn ir(&mut self) -> u8 {
self.ir
}
/// Returns the TCU
#[inline(always)]
pub fn tcu(&mut self) -> u8 {
self.tcu
}
/// Returns the processor status flags register (P)
#[inline(always)]
pub fn p(&self) -> u8 {
self.flags.as_byte()
}
/// Returns the Direct register (D)
#[inline(always)]
pub fn d(&self) -> u16 {
self.d
}
/// Returns the X index register
#[inline(always)]
pub fn x(&self) -> u16 {
self.x
}
/// Returns the Y index register
#[inline(always)]
pub fn y(&self) -> u16 {
self.y
}
/// Returns the stack pointer
#[inline(always)]
pub fn s(&self) -> u16 {
self.s
}
/// Returns the program counter
#[inline(always)]
pub fn pc(&self) -> u16 {
self.pc
}
/// Returns the program bank register
#[inline(always)]
pub fn pbr(&self) -> u8 {
self.pbr
}
/// Returns the K register (pbr/program bank register)
#[inline(always)]
pub fn k(&self) -> u8 {
self.pbr
}
/// Returns the data bank register
#[inline(always)]
pub fn dbr(&self) -> u8 {
self.dbr
}
/// Returns the accumulator
#[inline(always)]
pub fn c(&self) -> u16 {
self.a
}
/// Returns the accumulator's low byte
#[inline(always)]
pub fn a(&self) -> u8 {
self.a as u8
}
/// Returns the accumulator's high byte
#[inline(always)]
pub fn b(&self) -> u8 {
(self.a >> 8) as u8
}
}