Add support for enabling/disabling interrupts

old-bit-manip
Nick Krichevsky 2023-11-20 18:23:25 -05:00
parent 2db1e58568
commit 66c7e4287c
8 changed files with 131 additions and 8 deletions

View File

@ -15,6 +15,7 @@ pub struct Processor {
pub registers: Registers,
pub memory: Memory,
pub num_cycles: u128,
interrupt_enable_pending: bool,
}
impl Processor {
@ -32,9 +33,17 @@ impl Processor {
let (instruction, bytes_read) =
parse::next_instruction(&memory_view).expect("invalid instruction");
let interrupt_enable_pending_already = self.interrupt_enable_pending;
self.run_instruction(instruction);
let (next_pc, _carry) = pc.overflowing_add(bytes_read);
// EI does not work until after the following instruction, so we check after running an instruction if
// this is true
if interrupt_enable_pending_already && self.interrupt_enable_pending {
self.interrupt_enable_pending = false;
self.registers.enable_interrupts();
}
let next_pc = pc.wrapping_add(bytes_read);
self.registers.program_counter = next_pc;
}
@ -51,6 +60,20 @@ impl Processor {
self.num_cycles += u128::from(run_res.unwrap().0);
}
pub fn enable_interrupts(&mut self) {
self.interrupt_enable_pending = true;
}
pub fn disable_interrupts(&mut self) {
self.interrupt_enable_pending = false;
self.registers.disable_interrupts();
}
#[must_use]
pub fn interrupts_enabled(&mut self) -> bool {
self.registers.interrupts_enabled()
}
fn load_from_register_to_register_address(
&mut self,
register_with_dst_address: register::Combined,

View File

@ -8,4 +8,6 @@ pub enum MiscInstruction {
ComplementARegister,
DecimalAdjustAccumulator,
Nop,
EnableInterrupts,
DisableInterrupts,
}

View File

@ -15,6 +15,8 @@ impl OpcodeParser for Parser {
0x27 => Ok(build_daa_data()),
0x2F => Ok(build_complement_a_register_data()),
0x00 => Ok(build_nop_data()),
0xFB => Ok(build_enable_interrupts_data()),
0xF3 => Ok(build_disable_interrupts_data()),
_ => Err(super::Error::UnknownOpcode(opcode)),
}
}
@ -40,8 +42,13 @@ fn build_daa_data() -> ParseOutput {
}
fn build_nop_data() -> ParseOutput {
(
Instruction::Misc(MiscInstruction::Nop),
1,
)
(Instruction::Misc(MiscInstruction::Nop), 1)
}
fn build_enable_interrupts_data() -> ParseOutput {
(Instruction::Misc(MiscInstruction::EnableInterrupts), 1)
}
fn build_disable_interrupts_data() -> ParseOutput {
(Instruction::Misc(MiscInstruction::DisableInterrupts), 1)
}

View File

@ -8,9 +8,7 @@ use super::{Cycles, Run};
impl Run for MiscInstruction {
fn run_on(&self, processor: &mut Processor) -> Result<Cycles, Error> {
match *self {
MiscInstruction::Nop => {
Ok(Cycles(4))
}
MiscInstruction::Nop => Ok(Cycles(4)),
MiscInstruction::SetCarryFlag => {
set_flags_in_carry_bit_instruction(processor, 1);
@ -66,6 +64,18 @@ impl Run for MiscInstruction {
Ok(Cycles(4))
}
MiscInstruction::EnableInterrupts => {
processor.enable_interrupts();
Ok(Cycles(4))
},
MiscInstruction::DisableInterrupts => {
processor.disable_interrupts();
Ok(Cycles(4))
}
}
}
}

View File

@ -66,6 +66,7 @@ pub struct Registers {
pub program_counter: u16,
// the "F" register and the "flags" register are the same register
flags: u8,
interrupts_enabled: bool,
}
impl Flag {
@ -99,6 +100,7 @@ impl Default for Registers {
e: 0,
h: 0,
l: 0,
interrupts_enabled: false,
stack_pointer: 0,
program_counter: INITIAL_PROGRAM_COUNTER_VALUE,
flags: 0,
@ -264,6 +266,19 @@ impl Registers {
Combined::HL => (&mut self.h, &mut self.l),
}
}
pub(super) fn enable_interrupts(&mut self) {
self.interrupts_enabled = true;
}
pub(super) fn disable_interrupts(&mut self) {
self.interrupts_enabled = false;
}
#[must_use]
pub fn interrupts_enabled(&self) -> bool {
self.interrupts_enabled
}
}
#[cfg(test)]

View File

@ -171,3 +171,69 @@ fn test_nop_executes_successfully() {
// uhhh it does nothing
assert_eq!(processor.num_cycles, 4);
}
#[test]
fn test_enable_interrupts_enables_interrupts_on_the_following_instruction() {
let mut processor = Processor::default();
[
// Enable interrupts
0xFB, // Nop
0x00,
]
.iter()
.copied()
.enumerate()
.for_each(|(idx, opcode)| {
processor
.memory
.set(
usize::from(processor.registers.program_counter) + idx,
opcode,
)
.expect("could not program data");
});
assert!(!processor.interrupts_enabled());
processor.run_next_instruction();
assert!(!processor.interrupts_enabled());
processor.run_next_instruction();
assert!(processor.interrupts_enabled());
}
#[test]
fn test_disable_interrupts_disables_interrupts_once_enabled() {
let mut processor = Processor::default();
[
// Enable interrupts
0xFB, // Nop
0x00,
0xF3,
]
.iter()
.copied()
.enumerate()
.for_each(|(idx, opcode)| {
processor
.memory
.set(
usize::from(processor.registers.program_counter) + idx,
opcode,
)
.expect("could not program data");
});
assert!(!processor.interrupts_enabled());
processor.run_next_instruction();
assert!(!processor.interrupts_enabled());
processor.run_next_instruction();
assert!(processor.interrupts_enabled());
processor.run_next_instruction();
assert!(!processor.interrupts_enabled());
}