Skip to main content

k580_core/
state.rs

1use crate::{
2    CoreError, Flags, MachineCycleLayout, Memory64K, PortBus, RegisterName, Registers,
3    ValidationError,
4};
5
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct Cpu8080State {
8    pub registers: Registers,
9    pub pc: u16,
10    pub sp: u16,
11    pub flags: Flags,
12    pub memory: Memory64K,
13    pub interrupt_request_pending: bool,
14    pub interrupt_enable: bool,
15    pub interrupt_enable_pending: bool,
16    pub halted: bool,
17    pub cycle_count: u64,
18    pub interrupt_vector_byte: Option<u8>,
19    pub tact_phase: Option<u8>,
20    /// Last executed T-phase of the current/just-finished instruction.
21    /// `tact_phase` resets to `None` at boundaries; this field holds
22    /// `total - 1` at completion so the UI freezes on the final T.
23    /// `None` only on cold start / Reset.
24    pub last_completed_tact_phase: Option<u8>,
25    pub(crate) active_tacts_remaining: u8,
26    pub(crate) active_tacts_total: u8,
27    pub(crate) active_opcode: Option<u8>,
28    pub(crate) active_branch_taken: bool,
29    /// Mirror of the chip's IR: holds the last opcode fetched on M1
30    /// until the next M1. After `HLT` a `memory.read(pc)` look-ahead
31    /// would show NOP from blank RAM; the IR still reads `0x76`.
32    pub last_fetched_opcode: u8,
33    /// Mirror of the chip's data bus latch (D7-D0). After `HLT` it
34    /// must show `0x76`, not the byte at the new PC.
35    pub last_data_bus_byte: u8,
36    /// Mirror of the chip's address bus latch (A0-A15). PC, HL, SP,
37    /// and 16-bit immediates take turns on it. After `HLT` PC=halt+1
38    /// but the latch still shows the HLT address.
39    pub last_address_bus: u16,
40}
41
42/// `#[derive(Default)]` would yield `sp: 0`; reference uses `0xFFFF`.
43impl Default for Cpu8080State {
44    fn default() -> Self {
45        Self {
46            registers: Registers::default(),
47            pc: 0,
48            sp: Self::RESET_SP,
49            flags: Flags::default(),
50            memory: Memory64K::default(),
51            interrupt_request_pending: false,
52            interrupt_enable: false,
53            interrupt_enable_pending: false,
54            halted: false,
55            cycle_count: 0,
56            interrupt_vector_byte: None,
57            tact_phase: None,
58            last_completed_tact_phase: None,
59            active_tacts_remaining: 0,
60            active_tacts_total: 0,
61            active_opcode: None,
62            active_branch_taken: true,
63            last_fetched_opcode: 0,
64            last_data_bus_byte: 0,
65            last_address_bus: 0,
66        }
67    }
68}
69
70#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct InstructionOutcome {
72    pub opcode: Option<u8>,
73    pub mnemonic: String,
74    pub pc_before: u16,
75    pub pc_after: u16,
76    pub t_states: u8,
77    pub halted: bool,
78    pub interrupt_accepted: bool,
79}
80
81#[derive(Clone, Debug, PartialEq, Eq)]
82pub struct TactOutcome {
83    pub tact_phase: u8,
84    pub instruction_boundary: bool,
85    pub cycle_count: u64,
86}
87
88impl Cpu8080State {
89    /// 8080 leaves SP indeterminate on reset; the reference uses
90    /// `0xFFFF` so a stray `PUSH` lands in the high stack region.
91    pub const RESET_SP: u16 = 0xFFFF;
92
93    pub fn reset_cpu(&mut self) {
94        let memory = core::mem::take(&mut self.memory);
95        *self = Self {
96            memory,
97            sp: Self::RESET_SP,
98            ..Self::default()
99        };
100    }
101
102    pub fn reset_ram(&mut self) {
103        self.memory.clear();
104    }
105
106    pub fn request_interrupt(&mut self, vector_byte: u8) {
107        self.interrupt_request_pending = true;
108        self.interrupt_vector_byte = Some(vector_byte);
109    }
110
111    pub fn set_register(&mut self, register: RegisterName, value: u8) {
112        self.registers.set(register, value);
113    }
114
115    pub fn get_register(&self, register: RegisterName) -> u8 {
116        self.registers.get(register)
117    }
118
119    pub fn set_memory(&mut self, address: u16, value: u8) {
120        self.memory.write(address, value);
121    }
122
123    pub fn set_memory_block(&mut self, start: u16, values: &[u8]) -> Result<(), ValidationError> {
124        let end = u32::from(start) + values.len() as u32;
125        if end > Memory64K::SIZE as u32 {
126            return Err(ValidationError::MemoryRange { start, end });
127        }
128        self.memory.as_mut_slice()[start as usize..end as usize].copy_from_slice(values);
129        Ok(())
130    }
131
132    /// Mirrors both bus latches; executors must go through this so
133    /// the address/data buffers don't go stale on the UI.
134    pub(crate) fn bus_read(&mut self, address: u16) -> u8 {
135        let value = self.memory.read(address);
136        self.last_address_bus = address;
137        self.last_data_bus_byte = value;
138        value
139    }
140
141    pub(crate) fn bus_write(&mut self, address: u16, value: u8) {
142        self.memory.write(address, value);
143        self.last_address_bus = address;
144        self.last_data_bus_byte = value;
145    }
146
147    /// Two machine cycles low → high; latches end up holding the high byte.
148    pub(crate) fn bus_read_word(&mut self, address: u16) -> u16 {
149        let lo = self.bus_read(address);
150        let hi = self.bus_read(address.wrapping_add(1));
151        u16::from(lo) | (u16::from(hi) << 8)
152    }
153
154    pub(crate) fn fetch_opcode(&mut self) -> u8 {
155        let opcode = self.bus_read(self.pc);
156        self.last_fetched_opcode = opcode;
157        opcode
158    }
159
160    /// Side-effect-free read for UI/disassembler; executors go through
161    /// `bus_read*` / `bus_write*` / `fetch_opcode`.
162    pub fn peek(&self, address: u16) -> u8 {
163        self.memory.read(address)
164    }
165
166    pub fn step_instruction<B: PortBus>(
167        &mut self,
168        bus: &mut B,
169    ) -> Result<InstructionOutcome, CoreError> {
170        if self.active_tacts_remaining > 0 {
171            let remaining = self.active_tacts_remaining;
172            let total = self.active_tacts_total;
173            let outcome = self.execute_instruction_boundary(bus)?;
174            self.cycle_count += u64::from(remaining);
175            if total > 0 {
176                self.last_completed_tact_phase = Some(total - 1);
177            }
178            self.clear_active_tact();
179            return Ok(outcome);
180        }
181
182        let outcome = self.execute_instruction_boundary(bus)?;
183        self.cycle_count += u64::from(outcome.t_states);
184        if outcome.t_states > 0 {
185            self.last_completed_tact_phase = Some(outcome.t_states - 1);
186        }
187        Ok(outcome)
188    }
189
190    pub(crate) fn clear_active_tact(&mut self) {
191        self.active_tacts_remaining = 0;
192        self.active_tacts_total = 0;
193        self.active_opcode = None;
194        self.active_branch_taken = true;
195        self.tact_phase = None;
196    }
197
198    pub fn timing_opcode(&self) -> u8 {
199        self.active_opcode.unwrap_or(self.last_fetched_opcode)
200    }
201
202    pub fn timing_branch_taken(&self, layout: MachineCycleLayout, phase: u8) -> bool {
203        if self.active_tacts_remaining > 0 {
204            return self.active_branch_taken;
205        }
206        if let Some(not_taken) = layout.not_taken {
207            let not_taken_total: u8 = not_taken.iter().sum();
208            if phase < not_taken_total {
209                return false;
210            }
211        }
212        true
213    }
214
215    pub fn tact_walk_active(&self) -> bool {
216        self.active_tacts_remaining > 0
217    }
218
219    pub fn run_for_t_states<B: PortBus>(
220        &mut self,
221        bus: &mut B,
222        t_states: u64,
223    ) -> Result<(), CoreError> {
224        for _ in 0..t_states {
225            self.step_tact(bus)?;
226        }
227        Ok(())
228    }
229
230    pub fn run_until_halt<B: PortBus>(
231        &mut self,
232        bus: &mut B,
233        max_instructions: u64,
234    ) -> Result<u64, CoreError> {
235        let mut executed = 0;
236        while !self.halted && executed < max_instructions {
237            self.step_instruction(bus)?;
238            executed += 1;
239        }
240        Ok(executed)
241    }
242}