Skip to content

Commit ebaa17c

Browse files
committed
add interrupts and timers, passes blargg 2
1 parent adb71eb commit ebaa17c

File tree

12 files changed

+270
-29
lines changed

12 files changed

+270
-29
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
*.bin
22
*.pdf
3+
.vscode
4+
log
35
/target
6+
/roms

src/cpu.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
pub mod interrupts;
2+
pub mod timers;
3+
14
use std::num::Wrapping;
25

6+
use interrupts::Interrupts;
7+
use timers::Timers;
8+
39
use crate::{
410
instructions::{decode::decode_instruction_at_address, type_def::Immediate16},
511
machine::Machine,
@@ -9,19 +15,32 @@ use crate::{
915

1016
#[derive(Clone, Debug, Hash)]
1117
pub struct CPU {
18+
pub low_power_mode: bool,
19+
pub interrupts: Interrupts,
1220
pub memory: Memory,
1321
pub registers: Registers,
22+
pub timers: Timers,
1423
}
1524

1625
impl CPU {
1726
pub fn new() -> Self {
1827
CPU {
28+
low_power_mode: false,
29+
interrupts: Interrupts::new(),
1930
memory: Memory::new(),
2031
registers: Registers::new(),
32+
timers: Timers::new(),
2133
}
2234
}
2335

2436
pub fn execute_one_instruction(machine: &mut Machine) -> (u8, u8) {
37+
if machine.cpu.low_power_mode {
38+
if Interrupts::is_interrupt_pending(machine) {
39+
machine.cpu.low_power_mode = false;
40+
}
41+
// Forces the other components to move forward
42+
return (4, 1);
43+
}
2544
let next_instruction = decode_instruction_at_address(machine, machine.cpu.registers.pc);
2645
// This will be the default PC, unless instruction semantics overwrite it
2746
machine.cpu.registers.pc =

src/cpu/interrupts.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use std::num::Wrapping;
2+
3+
use crate::{instructions::type_def::Immediate16, machine::Machine};
4+
5+
use super::CPU;
6+
7+
pub const VBLANK_INTERRUPT_BIT: u8 = 0;
8+
const VBLANK_INTERRUPT_ADDRESS: u16 = 0x40;
9+
pub const STAT_INTERRUPT_BIT: u8 = 1;
10+
const STAT_INTERRUPT_ADDRESS: u16 = 0x48;
11+
pub const TIMER_INTERRUPT_BIT: u8 = 2;
12+
const TIMER_INTERRUPT_ADDRESS: u16 = 0x50;
13+
pub const SERIAL_INTERRUPT_BIT: u8 = 3;
14+
const SERIAL_INTERRUPT_ADDRESS: u16 = 0x58;
15+
pub const JOYPAD_INTERRUPT_BIT: u8 = 4;
16+
const JOYPAD_INTERRUPT_ADDRESS: u16 = 0x60;
17+
18+
#[derive(Clone, Debug, Hash)]
19+
pub struct Interrupts {
20+
pub interrupt_master_enable: bool,
21+
pub interrupt_master_enable_delayed: bool,
22+
pub interrupt_enable: Wrapping<u8>,
23+
pub interrupt_flag: Wrapping<u8>,
24+
}
25+
26+
// Returns the bit index of the interrupt to handle (0 = VBlank... 4 = Joypad)
27+
fn should_handle_interrupt(machine: &mut Machine) -> Option<u8> {
28+
if !machine.cpu.interrupts.interrupt_master_enable {
29+
return None;
30+
}
31+
let masked_ie = machine.cpu.interrupts.interrupt_enable.0 & 0x1F;
32+
let masked_if = machine.cpu.interrupts.interrupt_flag.0 & 0x1F;
33+
let conjoined = masked_ie & masked_if;
34+
// 0 has most priority, 4 has least
35+
for i in 0..5 {
36+
let mask = 1 << i;
37+
if (conjoined & mask) == mask {
38+
return Some(i);
39+
}
40+
}
41+
None
42+
}
43+
44+
fn interrupt_handler_offset(interrupt_bit: u8) -> Wrapping<u16> {
45+
Wrapping(match interrupt_bit {
46+
VBLANK_INTERRUPT_BIT => VBLANK_INTERRUPT_ADDRESS,
47+
STAT_INTERRUPT_BIT => STAT_INTERRUPT_ADDRESS,
48+
TIMER_INTERRUPT_BIT => TIMER_INTERRUPT_ADDRESS,
49+
SERIAL_INTERRUPT_BIT => SERIAL_INTERRUPT_ADDRESS,
50+
JOYPAD_INTERRUPT_BIT => JOYPAD_INTERRUPT_ADDRESS,
51+
_ => unreachable!(),
52+
})
53+
}
54+
55+
impl Interrupts {
56+
pub fn new() -> Self {
57+
Interrupts {
58+
interrupt_master_enable: false,
59+
interrupt_master_enable_delayed: false,
60+
interrupt_enable: Wrapping(0),
61+
interrupt_flag: Wrapping(0),
62+
}
63+
}
64+
65+
pub fn handle_interrupts(machine: &mut Machine) -> (u8, u8) {
66+
if let Some(interrupt) = should_handle_interrupt(machine) {
67+
println!("Handling interrupt {}", interrupt);
68+
machine.cpu.interrupts.interrupt_flag =
69+
machine.cpu.interrupts.interrupt_flag & Wrapping(!(1 << interrupt));
70+
machine.cpu.interrupts.interrupt_master_enable = false;
71+
// Here the CPU:
72+
// - NOPs twice (2 M-cycles)
73+
// - PUSHes PC (2 M-cycles)
74+
// - sets PC to the handle (1 M-cycle)
75+
// Currently simulating this whole thing at once, but might need granularity
76+
CPU::push_imm16(machine, Immediate16::from_u16(machine.cpu.registers.pc));
77+
machine.cpu.registers.pc = interrupt_handler_offset(interrupt);
78+
(20, 5)
79+
} else {
80+
(0, 0)
81+
}
82+
}
83+
84+
pub fn is_interrupt_pending(machine: &Machine) -> bool {
85+
let masked_ie = machine.cpu.interrupts.interrupt_enable.0 & 0x1F;
86+
let masked_if = machine.cpu.interrupts.interrupt_flag.0 & 0x1F;
87+
(masked_ie & masked_if) != 0
88+
}
89+
90+
pub fn request_interrupt(&mut self, interrupt_bit: u8) {
91+
self.interrupt_flag |= 1 << interrupt_bit;
92+
}
93+
}

src/cpu/timers.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use std::num::Wrapping;
2+
3+
use crate::machine::Machine;
4+
5+
use super::interrupts::TIMER_INTERRUPT_BIT;
6+
7+
const DIVIDE_REGISTER_ADDRESS: u16 = 0xFF04;
8+
const TIMER_COUNTER_ADDRESS: u16 = 0xFF05;
9+
const TIMER_MODULO_ADDRESS: u16 = 0xFF06;
10+
const TIMER_CONTROL_ADDRESS: u16 = 0xFF07;
11+
12+
#[derive(Clone, Debug, Hash)]
13+
pub struct Timers {
14+
pub divide_register: Wrapping<u8>,
15+
divide_register_dots: u16,
16+
// When we reset this, we must account for the fact that the reset would happen at the end of
17+
// the resetting instruction, rather than the beginning. So we mark this to know to reset it
18+
// later.
19+
divide_register_to_be_reset: bool,
20+
pub timer_counter: Wrapping<u8>,
21+
timer_counter_dots: u16,
22+
pub timer_modulo: Wrapping<u8>,
23+
pub timer_control: Wrapping<u8>,
24+
}
25+
26+
fn get_timer_counter_threshold(machine: &mut Machine) -> u16 {
27+
match machine.cpu.timers.timer_control.0 & 0x3 {
28+
0b00 => 1024,
29+
0b01 => 16,
30+
0b10 => 64,
31+
0b11 => 256,
32+
_ => unreachable!(),
33+
}
34+
}
35+
36+
impl Timers {
37+
pub fn new() -> Self {
38+
Timers {
39+
divide_register: Wrapping(0),
40+
divide_register_to_be_reset: false,
41+
divide_register_dots: 0,
42+
timer_counter: Wrapping(0),
43+
timer_counter_dots: 0,
44+
timer_modulo: Wrapping(0),
45+
timer_control: Wrapping(0),
46+
}
47+
}
48+
49+
fn step_one_dot(machine: &mut Machine) {
50+
machine.cpu.timers.divide_register_dots += 1;
51+
if machine.cpu.timers.divide_register_dots == 256 {
52+
machine.cpu.timers.divide_register_dots = 0;
53+
machine.cpu.timers.divide_register += 1;
54+
}
55+
56+
if (machine.cpu.timers.timer_control.0 & 0b100) != 0 {
57+
machine.cpu.timers.timer_counter_dots += 1;
58+
if machine.cpu.timers.timer_counter_dots == get_timer_counter_threshold(machine) {
59+
machine.cpu.timers.timer_counter_dots = 0;
60+
machine.cpu.timers.timer_counter += 1;
61+
if machine.cpu.timers.timer_counter.0 == 0 {
62+
machine.cpu.timers.timer_counter = machine.cpu.timers.timer_modulo;
63+
machine.request_interrupt(TIMER_INTERRUPT_BIT);
64+
}
65+
}
66+
}
67+
}
68+
69+
pub fn step_dots(machine: &mut Machine, dots: u8) {
70+
for _ in 0..dots {
71+
Self::step_one_dot(machine);
72+
}
73+
if machine.cpu.timers.divide_register_to_be_reset {
74+
machine.cpu.timers.divide_register_to_be_reset = false;
75+
machine.cpu.timers.divide_register = Wrapping(0);
76+
}
77+
}
78+
79+
pub fn read_u8(&self, address: Wrapping<u16>) -> Wrapping<u8> {
80+
match address.0 {
81+
DIVIDE_REGISTER_ADDRESS => self.divide_register,
82+
TIMER_COUNTER_ADDRESS => self.timer_counter,
83+
TIMER_MODULO_ADDRESS => self.timer_modulo,
84+
TIMER_CONTROL_ADDRESS => self.timer_control,
85+
_ => unreachable!(),
86+
}
87+
}
88+
89+
pub fn write_u8(&mut self, address: Wrapping<u16>, value: Wrapping<u8>) {
90+
match address.0 {
91+
DIVIDE_REGISTER_ADDRESS => {
92+
// Writing any value to this register resets it. However, if we were to reset it
93+
// here for a 4 t-cycle instruction, it would have started counting 4 by the time
94+
// where it should actually be reset. So instead we mark it to be reset after
95+
// simulating the current instruction's t-cycles.
96+
self.divide_register_to_be_reset = true;
97+
}
98+
TIMER_COUNTER_ADDRESS => self.timer_counter = value,
99+
TIMER_MODULO_ADDRESS => self.timer_modulo = value,
100+
TIMER_CONTROL_ADDRESS => self.timer_control = value,
101+
_ => unreachable!(),
102+
}
103+
}
104+
}

src/instructions/decode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ pub fn decode_instruction_at_address(
178178
0x73 => Instruction::LD_mr16_r8(R16::HL, R8::E),
179179
0x74 => Instruction::LD_mr16_r8(R16::HL, R8::H),
180180
0x75 => Instruction::LD_mr16_r8(R16::HL, R8::L),
181-
0x76 => todo!(),
181+
0x76 => Instruction::HALT,
182182
0x77 => Instruction::LD_mr16_r8(R16::HL, R8::A),
183183
0x78 => Instruction::LD_r8_r8(R8::A, R8::B),
184184
0x79 => Instruction::LD_r8_r8(R8::A, R8::C),

src/instructions/display.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ impl DecodedInstruction {
3333
Instruction::DEC_r8(r8) => format!("DEC {}", r8),
3434
Instruction::DI => String::from("DI"),
3535
Instruction::EI => String::from("EI"),
36+
Instruction::HALT => String::from("HALT"),
3637
Instruction::Illegal(opcode) => format!("ILLEGAL 0x{:02X}", opcode),
3738
Instruction::INC_mHL => String::from("INC [HL]"),
3839
Instruction::INC_r16(r16) => format!("INC {}", r16),

src/instructions/semantics.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::num::Wrapping;
22

33
use crate::{
4-
cpu::CPU,
4+
cpu::{interrupts::Interrupts, CPU},
55
machine::Machine,
66
registers::{Flag, R16, R8},
77
};
@@ -114,6 +114,12 @@ fn call(machine: &mut Machine, address: Wrapping<u16>) {
114114

115115
impl Instruction {
116116
pub fn execute(self: &Instruction, machine: &mut Machine) -> (u8, u8) {
117+
// EI effects are delayed by one instruction, we resolve it here
118+
if machine.cpu.interrupts.interrupt_master_enable_delayed {
119+
machine.cpu.interrupts.interrupt_master_enable_delayed = false;
120+
machine.cpu.interrupts.interrupt_master_enable = true;
121+
}
122+
117123
match self {
118124
Instruction::ADC_A_r8(r8) => {
119125
let a = machine.cpu.registers.read_a();
@@ -280,13 +286,26 @@ impl Instruction {
280286
}
281287

282288
Instruction::DI => {
283-
machine.cpu.registers.ime = false;
289+
machine.cpu.interrupts.interrupt_master_enable = false;
284290
(4, 1)
285291
}
286292

293+
// NOTE: This sets up IME in one instruction
287294
Instruction::EI => {
288-
// FIXME: This should apparently be delayed until the next instruction has finished.
289-
machine.cpu.registers.ime = true;
295+
machine.cpu.interrupts.interrupt_master_enable_delayed = true;
296+
(4, 1)
297+
}
298+
299+
Instruction::HALT => {
300+
if machine.cpu.interrupts.interrupt_master_enable {
301+
machine.cpu.low_power_mode = true;
302+
} else {
303+
if Interrupts::is_interrupt_pending(machine) {
304+
panic!("Need to emulate HALT bug");
305+
} else {
306+
machine.cpu.low_power_mode = true;
307+
}
308+
}
290309
(4, 1)
291310
}
292311

@@ -562,8 +581,7 @@ impl Instruction {
562581
}
563582

564583
Instruction::RETI => {
565-
// TODO: Handle IME delay
566-
machine.cpu.registers.ime = true;
584+
machine.cpu.interrupts.interrupt_master_enable = true;
567585
CPU::pop_r16(machine, &R16::PC);
568586
(16, 4)
569587
}

src/instructions/type_def.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ pub enum Instruction {
5858
DEC_r8(R8),
5959
DI,
6060
EI,
61+
HALT,
6162
INC_mHL,
6263
INC_r16(R16),
6364
INC_r8(R8),

0 commit comments

Comments
 (0)