rustzx_z80/
cpu.rs

1//! Z80 CPU module
2
3use crate::{
4    opcode::{
5        execute_bits, execute_extended, execute_normal, execute_pop_16, execute_push_16, Opcode,
6        Prefix,
7    },
8    RegName16, Regs, Z80Bus,
9};
10
11/// Interrupt mode enum
12#[derive(Debug, Clone, Copy)]
13pub enum IntMode {
14    Im0,
15    Im1,
16    Im2,
17}
18
19impl From<IntMode> for u8 {
20    fn from(mode: IntMode) -> Self {
21        match mode {
22            IntMode::Im0 => 0,
23            IntMode::Im1 => 1,
24            IntMode::Im2 => 2,
25        }
26    }
27}
28
29/// Z80 Processor struct
30pub struct Z80 {
31    /// Contains Z80 registers data
32    pub regs: Regs,
33    /// active if Z80 waiting for interrupt
34    pub(crate) halted: bool,
35    /// enabled if interrupt check will be skipped nex time
36    pub(crate) skip_interrupt: bool,
37    /// type of interrupt
38    pub(crate) int_mode: IntMode,
39    active_prefix: Prefix,
40}
41
42impl Default for Z80 {
43    fn default() -> Self {
44        Self {
45            regs: Regs::default(),
46            halted: false,
47            skip_interrupt: false,
48            int_mode: IntMode::Im0,
49            active_prefix: Prefix::None,
50        }
51    }
52}
53
54impl Z80 {
55    /// Reads byte from memory and increments PC
56    #[inline]
57    pub(crate) fn fetch_byte(&mut self, bus: &mut impl Z80Bus, clk: usize) -> u8 {
58        let addr = self.regs.get_pc();
59        self.regs.inc_pc();
60        bus.read(addr, clk)
61    }
62
63    /// Reads word from memory and increments PC twice
64    #[inline]
65    pub(crate) fn fetch_word(&mut self, bus: &mut impl Z80Bus, clk: usize) -> u16 {
66        let (hi_addr, lo_addr);
67        lo_addr = self.regs.get_pc();
68        let lo = bus.read(lo_addr, clk);
69        hi_addr = self.regs.inc_pc();
70        let hi = bus.read(hi_addr, clk);
71        self.regs.inc_pc();
72        u16::from_le_bytes([lo, hi])
73    }
74
75    /// Checks is cpu halted
76    pub fn is_halted(&self) -> bool {
77        self.halted
78    }
79
80    /// Returns current interrupt mode
81    pub fn get_im(&self) -> IntMode {
82        self.int_mode
83    }
84
85    /// Changes interrupt mode
86    pub fn set_im(&mut self, value: u8) {
87        assert!(value < 3);
88        self.int_mode = match value {
89            0 => IntMode::Im0,
90            1 => IntMode::Im1,
91            2 => IntMode::Im2,
92            _ => unreachable!(),
93        }
94    }
95
96    /// Pops program counter to the stack. Exposed as a public crate interface to support
97    /// 48K SNA loading in `rustzx-core` and fast tape loaders (Perform RET)
98    pub fn pop_pc_from_stack(&mut self, bus: &mut impl Z80Bus) {
99        execute_pop_16(self, bus, RegName16::PC, 0);
100    }
101
102    /// Pushes program counter from the stack. Exposed as a public crate interface to support
103    /// 48K SNA saving in `rustzx-core`
104    pub fn push_pc_to_stack(&mut self, bus: &mut impl Z80Bus) {
105        execute_push_16(self, bus, RegName16::PC, 0);
106    }
107
108    fn handle_interrupt(&mut self, bus: &mut impl Z80Bus) {
109        if bus.nmi_active() {
110            // q resets during interrupt
111            self.regs.clear_q();
112            // Release halt line on the bus
113            if self.halted {
114                bus.halt(false);
115                self.halted = false;
116                self.regs.inc_pc();
117            }
118            // push pc and set pc to 0x0066
119            bus.wait_loop(self.regs.get_pc(), 5);
120            self.regs.set_iff1(false);
121            // 3 x 2 clocks consumed
122            execute_push_16(self, bus, RegName16::PC, 3);
123            self.regs.set_pc(0x0066);
124
125            // mem_ptr is set to PC
126            self.regs.set_mem_ptr(self.regs.get_pc());
127
128            self.regs.inc_r();
129            // 5 + 3 + 3 = 11 clocks
130        } else if bus.int_active() && self.regs.get_iff1() {
131            // q resets during interrupt
132            self.regs.clear_q();
133            // Release halt line on the bus
134            if self.halted {
135                bus.halt(false);
136                self.halted = false;
137                self.regs.inc_pc();
138            }
139            self.regs.inc_r();
140            self.regs.set_iff1(false);
141            self.regs.set_iff2(false);
142            match self.int_mode {
143                // For zx spectrum both Im0 and Im1 are same
144                IntMode::Im0 | IntMode::Im1 => {
145                    execute_push_16(self, bus, RegName16::PC, 3);
146                    self.regs.set_pc(0x0038);
147
148                    // 3 + 3 + 7 = 13 clocks
149                    bus.wait_internal(7);
150                }
151                // jump using interrupt vector
152                IntMode::Im2 => {
153                    execute_push_16(self, bus, RegName16::PC, 3);
154                    // build interrupt vector
155                    let addr = (((self.regs.get_i() as u16) << 8) & 0xFF00)
156                        | ((bus.read_interrupt() as u16) & 0x00FF);
157                    let addr = bus.read_word(addr, 3);
158                    self.regs.set_pc(addr);
159                    bus.wait_internal(7);
160                    // 3 + 3 + 3 + 3 + 7 = 19 clocks
161                }
162            }
163            // mem_ptr is set to PC
164            self.regs.set_mem_ptr(self.regs.get_pc());
165        }
166    }
167
168    /// Perform next emulation step
169    pub fn emulate(&mut self, bus: &mut impl Z80Bus) {
170        // check interrupts
171        if !self.skip_interrupt {
172            self.handle_interrupt(bus);
173        } else {
174            // allow interrupts again
175            self.skip_interrupt = false;
176        };
177
178        // Actions to be performed before any opcode execution
179        let before_execute_opcode = |cpu: &mut Self| {
180            // Save Q register value from previous emulation step, which is later used to
181            // properly calculate flags in some instructions
182            cpu.regs.step_q();
183        };
184
185        let byte1 = if self.active_prefix != Prefix::None {
186            let tmp = self.active_prefix.to_byte().unwrap();
187            self.active_prefix = Prefix::None;
188            tmp
189        } else {
190            self.regs.inc_r();
191            self.fetch_byte(bus, 4)
192        };
193        let prefix_hi = Prefix::from_byte(byte1);
194        if prefix_hi != Prefix::None {
195            match prefix_hi {
196                prefix_single @ Prefix::DD | prefix_single @ Prefix::FD => {
197                    let byte2 = self.fetch_byte(bus, 4);
198                    self.regs.inc_r();
199                    let prefix_lo = Prefix::from_byte(byte2);
200                    match prefix_lo {
201                        Prefix::DD | Prefix::ED | Prefix::FD => {
202                            self.active_prefix = prefix_lo;
203                            self.skip_interrupt = true;
204                        }
205                        Prefix::CB => {
206                            before_execute_opcode(self);
207                            execute_bits(self, bus, prefix_single);
208                        }
209                        Prefix::None => {
210                            let opcode = Opcode::from_byte(byte2);
211                            before_execute_opcode(self);
212                            execute_normal(self, bus, opcode, prefix_single);
213                        }
214                    };
215                }
216                Prefix::CB => {
217                    // opcode will be read in the called
218                    before_execute_opcode(self);
219                    execute_bits(self, bus, Prefix::None);
220                }
221                Prefix::ED => {
222                    let byte2 = self.fetch_byte(bus, 4);
223                    self.regs.inc_r();
224                    let opcode = Opcode::from_byte(byte2);
225                    before_execute_opcode(self);
226                    execute_extended(self, bus, opcode);
227                }
228                _ => unreachable!(),
229            };
230        } else {
231            let opcode = Opcode::from_byte(byte1);
232            before_execute_opcode(self);
233            execute_normal(self, bus, opcode, Prefix::None);
234        };
235        // Allow bus implementation to process pc-based events
236        bus.pc_callback(self.regs.get_pc());
237    }
238}