Add support for enabling/disabling interrupts
parent
2db1e58568
commit
66c7e4287c
25
src/cpu.rs
25
src/cpu.rs
|
@ -15,6 +15,7 @@ pub struct Processor {
|
||||||
pub registers: Registers,
|
pub registers: Registers,
|
||||||
pub memory: Memory,
|
pub memory: Memory,
|
||||||
pub num_cycles: u128,
|
pub num_cycles: u128,
|
||||||
|
interrupt_enable_pending: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Processor {
|
impl Processor {
|
||||||
|
@ -32,9 +33,17 @@ impl Processor {
|
||||||
let (instruction, bytes_read) =
|
let (instruction, bytes_read) =
|
||||||
parse::next_instruction(&memory_view).expect("invalid instruction");
|
parse::next_instruction(&memory_view).expect("invalid instruction");
|
||||||
|
|
||||||
|
let interrupt_enable_pending_already = self.interrupt_enable_pending;
|
||||||
self.run_instruction(instruction);
|
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;
|
self.registers.program_counter = next_pc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +60,20 @@ impl Processor {
|
||||||
self.num_cycles += u128::from(run_res.unwrap().0);
|
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(
|
fn load_from_register_to_register_address(
|
||||||
&mut self,
|
&mut self,
|
||||||
register_with_dst_address: register::Combined,
|
register_with_dst_address: register::Combined,
|
||||||
|
|
|
@ -8,4 +8,6 @@ pub enum MiscInstruction {
|
||||||
ComplementARegister,
|
ComplementARegister,
|
||||||
DecimalAdjustAccumulator,
|
DecimalAdjustAccumulator,
|
||||||
Nop,
|
Nop,
|
||||||
|
EnableInterrupts,
|
||||||
|
DisableInterrupts,
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ impl OpcodeParser for Parser {
|
||||||
0x27 => Ok(build_daa_data()),
|
0x27 => Ok(build_daa_data()),
|
||||||
0x2F => Ok(build_complement_a_register_data()),
|
0x2F => Ok(build_complement_a_register_data()),
|
||||||
0x00 => Ok(build_nop_data()),
|
0x00 => Ok(build_nop_data()),
|
||||||
|
0xFB => Ok(build_enable_interrupts_data()),
|
||||||
|
0xF3 => Ok(build_disable_interrupts_data()),
|
||||||
_ => Err(super::Error::UnknownOpcode(opcode)),
|
_ => Err(super::Error::UnknownOpcode(opcode)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,8 +42,13 @@ fn build_daa_data() -> ParseOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_nop_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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,7 @@ use super::{Cycles, Run};
|
||||||
impl Run for MiscInstruction {
|
impl Run for MiscInstruction {
|
||||||
fn run_on(&self, processor: &mut Processor) -> Result<Cycles, Error> {
|
fn run_on(&self, processor: &mut Processor) -> Result<Cycles, Error> {
|
||||||
match *self {
|
match *self {
|
||||||
MiscInstruction::Nop => {
|
MiscInstruction::Nop => Ok(Cycles(4)),
|
||||||
Ok(Cycles(4))
|
|
||||||
}
|
|
||||||
|
|
||||||
MiscInstruction::SetCarryFlag => {
|
MiscInstruction::SetCarryFlag => {
|
||||||
set_flags_in_carry_bit_instruction(processor, 1);
|
set_flags_in_carry_bit_instruction(processor, 1);
|
||||||
|
@ -66,6 +64,18 @@ impl Run for MiscInstruction {
|
||||||
|
|
||||||
Ok(Cycles(4))
|
Ok(Cycles(4))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MiscInstruction::EnableInterrupts => {
|
||||||
|
processor.enable_interrupts();
|
||||||
|
|
||||||
|
Ok(Cycles(4))
|
||||||
|
},
|
||||||
|
|
||||||
|
MiscInstruction::DisableInterrupts => {
|
||||||
|
processor.disable_interrupts();
|
||||||
|
|
||||||
|
Ok(Cycles(4))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,7 @@ pub struct Registers {
|
||||||
pub program_counter: u16,
|
pub program_counter: u16,
|
||||||
// the "F" register and the "flags" register are the same register
|
// the "F" register and the "flags" register are the same register
|
||||||
flags: u8,
|
flags: u8,
|
||||||
|
interrupts_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Flag {
|
impl Flag {
|
||||||
|
@ -99,6 +100,7 @@ impl Default for Registers {
|
||||||
e: 0,
|
e: 0,
|
||||||
h: 0,
|
h: 0,
|
||||||
l: 0,
|
l: 0,
|
||||||
|
interrupts_enabled: false,
|
||||||
stack_pointer: 0,
|
stack_pointer: 0,
|
||||||
program_counter: INITIAL_PROGRAM_COUNTER_VALUE,
|
program_counter: INITIAL_PROGRAM_COUNTER_VALUE,
|
||||||
flags: 0,
|
flags: 0,
|
||||||
|
@ -264,6 +266,19 @@ impl Registers {
|
||||||
Combined::HL => (&mut self.h, &mut self.l),
|
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)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -171,3 +171,69 @@ fn test_nop_executes_successfully() {
|
||||||
// uhhh it does nothing
|
// uhhh it does nothing
|
||||||
assert_eq!(processor.num_cycles, 4);
|
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());
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue