aboutsummaryrefslogtreecommitdiff
path: root/lib/vnf/src
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mssola@mssola.com>2026-08-27 07:12:42 +0200
committerMiquel Sabaté Solà <mssola@mssola.com>2026-08-27 07:12:42 +0200
commit50ebac34fec99c853b2f010bfd100c11b5e0a603 (patch)
tree2353404e148fcf56019c478b05a449ad6331ea2e /lib/vnf/src
parent1bc9db2e8de21995fef2a4f59d8e1d20eef7d470 (diff)
downloadtools.nes-50ebac34fec99c853b2f010bfd100c11b5e0a603.tar.gz
tools.nes-50ebac34fec99c853b2f010bfd100c11b5e0a603.zip
vnf: add support for all stack instructions
This also includes brk and rti, for which we are also storing and exporting the break mark, just in case it can be useful to library users in case they want to add error codes in break marks. This commit also includes a fix for accounting the Z and N flags on transfer instructions, which couldn't easily be split into a separate commit, but it's a fairly simple change. Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
Diffstat (limited to 'lib/vnf/src')
-rw-r--r--lib/vnf/src/lib.rs179
1 files changed, 156 insertions, 23 deletions
diff --git a/lib/vnf/src/lib.rs b/lib/vnf/src/lib.rs
index b1b596b..e9c7539 100644
--- a/lib/vnf/src/lib.rs
+++ b/lib/vnf/src/lib.rs
@@ -17,6 +17,9 @@ pub struct StatusRegister {
pub interrupt: bool,
pub zero: bool,
pub carry: bool,
+
+ /// The byte after a 'brk' instruction.
+ pub break_mark: u8,
}
impl Default for StatusRegister {
@@ -29,11 +32,24 @@ impl Default for StatusRegister {
interrupt: true,
zero: false,
carry: false,
+ break_mark: 0,
}
}
}
impl StatusRegister {
+ // Bitmap constants for how the different bools are laid out on the Status
+ // Register. These are defined like so: (from most to least significant):
+ // N V - B D I Z C
+ pub const CARRY: u8 = 1 << 0;
+ pub const ZERO: u8 = 1 << 1;
+ pub const INTERRUPT: u8 = 1 << 2;
+ pub const DECIMAL: u8 = 1 << 3;
+ pub const BRK: u8 = 1 << 4;
+ // NOTE: bit 5 unused.
+ pub const OVERFLOW: u8 = 1 << 6;
+ pub const NEGATIVE: u8 = 1 << 7;
+
/// Returns a string with the initial letter for each status bit that is
/// set. Otherwise, for unset bits, a '-' is given.
fn humanize(&self) -> String {
@@ -145,6 +161,7 @@ pub struct MemoryPolicy {
impl Default for MemoryPolicy {
fn default() -> Self {
+ #[allow(clippy::single_range_in_vec_init)]
Self {
initial_value: MemoryInitialValue::Fixed(0),
allowed_reads: vec![(0..0x800)],
@@ -298,6 +315,11 @@ fn init_memory(policy: &MemoryPolicy) -> Vec<MemoryCell> {
});
}
+ // Force these initial stack values as these should be the values regardless
+ // of the memory policy, as it's per 6502 specification..
+ vec[0x2FF].value = 0x00;
+ vec[0x2FE].value = 0x00;
+
vec
}
@@ -661,6 +683,67 @@ impl Machine {
self.read_memory(address, account_read)
}
+ // Push the given address onto the stack.
+ fn push_address(&mut self, address: usize) -> Result<(), String> {
+ let low = (address as u16 & 0x00FF) as u8;
+ let high = ((address as u16 & 0xFF00) >> 8) as u8;
+
+ self.push_stack(high, false)?;
+ self.push_stack(low, true)?;
+
+ Ok(())
+ }
+
+ // Push the status register onto the stack.
+ fn push_status_register(&mut self) -> Result<(), String> {
+ // brk flag and bit 5 are always set on 'php'/'brk' as per 6502
+ // specification.
+ let mut val = 0b00110000;
+ self.status_register.brk = true;
+
+ if self.status_register.carry {
+ val |= StatusRegister::CARRY;
+ }
+ if self.status_register.zero {
+ val |= StatusRegister::ZERO;
+ }
+ if self.status_register.interrupt {
+ val |= StatusRegister::INTERRUPT;
+ }
+ if self.status_register.decimal {
+ val |= StatusRegister::DECIMAL;
+ }
+ if self.status_register.overflow {
+ val |= StatusRegister::OVERFLOW;
+ }
+ if self.status_register.negative {
+ val |= StatusRegister::NEGATIVE;
+ }
+ self.push_stack(val, true)?;
+ Ok(())
+ }
+
+ // Pull the status register from the stack.
+ fn pop_status_register(&mut self) -> Result<(), String> {
+ let val = self.pop_stack(true, true)?;
+
+ self.status_register.carry = (val & StatusRegister::CARRY) == StatusRegister::CARRY;
+ self.status_register.zero = (val & StatusRegister::ZERO) == StatusRegister::ZERO;
+ self.status_register.interrupt =
+ (val & StatusRegister::INTERRUPT) == StatusRegister::INTERRUPT;
+ self.status_register.decimal = (val & StatusRegister::DECIMAL) == StatusRegister::DECIMAL;
+ self.status_register.overflow =
+ (val & StatusRegister::OVERFLOW) == StatusRegister::OVERFLOW;
+ self.status_register.negative =
+ (val & StatusRegister::NEGATIVE) == StatusRegister::NEGATIVE;
+
+ // BRK is always cleared. We also clear the break mark now.
+ self.status_register.brk = false;
+ self.status_register.break_mark = 0;
+
+ Ok(())
+ }
+
// Returns true of the stack is empty, false otherwise. Note that this
// just means that the value of the 's' register is the one set as its
// initial value.
@@ -686,14 +769,8 @@ impl Machine {
match self.current_instruction.identifier {
// TODO
- InstructionIdentifier::Brk => todo!(),
InstructionIdentifier::Bvc => todo!(),
InstructionIdentifier::Bvs => todo!(),
- InstructionIdentifier::Pha => todo!(),
- InstructionIdentifier::Pla => todo!(),
- InstructionIdentifier::Php => todo!(),
- InstructionIdentifier::Plp => todo!(),
- InstructionIdentifier::Rti => todo!(),
// Flag instructions.
InstructionIdentifier::Sec => self.status_register.carry = true,
@@ -913,16 +990,12 @@ impl Machine {
}
// NOTE: as per 6502 specification, the address should be - 1
- // because the 'rts'/'rti' instruction will be the one in charge
- // of adding its size to the end PC upon execution. This is kind
- // of pedantic but in the end we record the address being pushed
+ // because the 'rts' instruction will be the one in charge of
+ // adding its size to the end PC upon execution. This is kind of
+ // pedantic but in the end we record the address being pushed
// onto the stack and that should be precise.
let next_address = self.pc + self.current_instruction.size as usize - 1;
- let low = (next_address as u16 & 0x00FF) as u8;
- let high = ((next_address as u16 & 0xFF00) >> 8) as u8;
-
- self.push_stack(high, false)?;
- self.push_stack(low, true)?;
+ self.push_address(next_address)?;
self.pc = address;
self.skip_pc = true;
@@ -980,24 +1053,84 @@ impl Machine {
//
// NOTE: as per 6502 specification, the address saved onto the
// stack was the next instruction before the call, but the size
- // of the 'rts/rti' should also be accounted. Hence the + 1 to
- // the resulting PC.
- let low = self.pop_stack(false, false)? as u16;
- let high = (self.pop_stack(false, true)? as u16) << 8;
+ // of the 'rts' should also be accounted. Hence the + 1 to the
+ // resulting PC.
+ let low = self.pop_stack(true, false)? as u16;
+ let high = (self.pop_stack(true, true)? as u16) << 8;
self.pc = (high + low) as usize + 1;
self.skip_pc = true;
}
+ // Stack
+ InstructionIdentifier::Pha => self.push_stack(self.a, true)?,
+ InstructionIdentifier::Pla => self.a = self.pop_stack(true, true)?,
+ InstructionIdentifier::Php => self.push_status_register()?,
+ InstructionIdentifier::Plp => self.pop_status_register()?,
+
+ InstructionIdentifier::Brk => {
+ // The next address after a 'brk' is: pc + size of 'brk' (1) +
+ // break mark (1).
+ let next_address = self.pc + 2;
+ self.push_address(next_address)?;
+ self.push_status_register()?;
+
+ // The break mark is basically the byte which is in current PC +
+ // 1. If that's not possible, then we have a 'brk' as the last
+ // instruction with no break mark or something like that, which
+ // is just nonsense.
+ self.status_register.break_mark = *self
+ .prg_rom
+ .get(self.pc - 0x8000 + 1)
+ .expect("you need to reserve a byte for the break mark");
+
+ // TODO: the next PC is the advertized IRQ one. This one is
+ // picked as the last byte from PRG-ROM, but depending on how
+ // bank mapping is done, this is not necessarily true.
+ let len = self.prg_rom.len();
+ let high = &(self.prg_rom[len - 1] as usize) << 8;
+ let low = &(self.prg_rom[len - 2] as usize);
+ self.pc = high + low;
+ self.skip_pc = true;
+ }
+ InstructionIdentifier::Rti => {
+ self.pop_status_register()?;
+
+ let low = self.pop_stack(true, false)? as usize;
+ let high = (self.pop_stack(true, true)? as usize) << 8;
+ self.pc = high + low;
+ self.skip_pc = true;
+ }
+
// transfer
- InstructionIdentifier::Tax => self.x = self.a,
- InstructionIdentifier::Tay => self.y = self.a,
- InstructionIdentifier::Tsx => self.x = self.s,
- InstructionIdentifier::Txa => self.a = self.x,
+ InstructionIdentifier::Tax => {
+ self.x = self.a;
+ self.status_register.zero = self.x == 0;
+ self.status_register.negative = (self.x & 0x80) == 0x80;
+ }
+ InstructionIdentifier::Tay => {
+ self.y = self.a;
+ self.status_register.zero = self.y == 0;
+ self.status_register.negative = (self.y & 0x80) == 0x80;
+ }
+ InstructionIdentifier::Tsx => {
+ self.x = self.s;
+ self.status_register.zero = self.x == 0;
+ self.status_register.negative = (self.x & 0x80) == 0x80;
+ }
+ InstructionIdentifier::Txa => {
+ self.a = self.x;
+ self.status_register.zero = self.a == 0;
+ self.status_register.negative = (self.a & 0x80) == 0x80;
+ }
InstructionIdentifier::Txs => {
self.s = self.x;
self.initial_stack_value = self.x;
}
- InstructionIdentifier::Tya => self.a = self.y,
+ InstructionIdentifier::Tya => {
+ self.a = self.y;
+ self.status_register.zero = self.a == 0;
+ self.status_register.negative = (self.a & 0x80) == 0x80;
+ }
// other
InstructionIdentifier::Bit => {