#[allow(unused_imports)]
use log::{error, warn, info, debug, trace};
use crate::z80emu::{
Cpu, CpuDebug, CpuDebugFn, Memory, Io, BreakCause,
host::{
cycles::M1_CYCLE_TS, Result
}
};
use crate::bus::BusDevice;
use crate::chip::{MemoryAccess, ControlUnit};
use crate::clock::{
HALT_VC_THRESHOLD,
VideoTs, VFrameTs, Ts, VFrameTsCounter, MemoryContention
};
use crate::memory::MemoryExtension;
use crate::video::{Video, VideoFrame};
pub trait UlaControlExt: Video {
fn prepare_next_frame<C: MemoryContention>(
&mut self,
vtsc: VFrameTsCounter<Self::VideoFrame, C>
) -> VFrameTsCounter<Self::VideoFrame, C>;
fn ensure_next_frame_vtsc(&mut self) -> VFrameTsCounter<Self::VideoFrame, Self::Contention> {
let mut vtsc = self.current_video_clock();
if vtsc.is_eof() {
vtsc = self.prepare_next_frame(vtsc);
}
vtsc
}
}
pub(crate) trait UlaCpuExt: UlaControlExt {
fn ula_nmi<C: Cpu>(&mut self, cpu: &mut C) -> bool;
fn ula_execute_next_frame_with_breaks<C: Cpu>(
&mut self,
cpu: &mut C
) -> bool;
fn ula_execute_single_step<C: Cpu, F: FnOnce(CpuDebug)>(
&mut self,
cpu: &mut C,
debug: Option<F>
) -> Result<(),()>;
fn ula_execute_instruction<C: Cpu>(
&mut self,
cpu: &mut C,
code: u8
) -> Result<(), ()>;
#[inline]
fn ula_check_halt<C: Cpu>(mut vts: VideoTs, cpu: &mut C) -> VideoTs {
if vts.vc >= HALT_VC_THRESHOLD {
debug!("HALT FOREVER: {:?}", vts);
cpu.disable_interrupts();
cpu.halt();
vts.vc = (vts.vc - HALT_VC_THRESHOLD).max(Self::VideoFrame::VSL_COUNT);
}
vts
}
}
impl<U, B, X> UlaCpuExt for U
where U: UlaControlExt +
ControlUnit<BusDevice=B> +
MemoryAccess<MemoryExt=X> +
Memory<Timestamp=VideoTs> +
Io<Timestamp=VideoTs, WrIoBreak=(), RetiBreak=()>,
B: BusDevice,
B::Timestamp: From<VFrameTs<U::VideoFrame>>,
X: MemoryExtension
{
fn ula_nmi<C: Cpu>(&mut self, cpu: &mut C) -> bool {
let mut vtsc = self.ensure_next_frame_vtsc();
let res = cpu.nmi(self, &mut vtsc);
self.set_video_ts(vtsc.into());
self.bus_device_mut().update_timestamp(vtsc.vts.into());
res
}
fn ula_execute_next_frame_with_breaks<C: Cpu>(
&mut self,
cpu: &mut C
) -> bool
{
let mut vtsc = self.ensure_next_frame_vtsc();
while !vtsc.is_eof() {
let vc_limit = if vtsc.vc < 1 { 1 }
else {
Self::VideoFrame::VSL_COUNT
};
match cpu.execute_with_limit(self, &mut vtsc, vc_limit) {
Ok(()) => {
**vtsc = Self::ula_check_halt(vtsc.into(), cpu);
},
Err(BreakCause::Halt) => {
if vtsc.vc < 1 {
continue
}
}
Err(_) => {
**vtsc = Self::ula_check_halt(vtsc.into(), cpu);
if vtsc.is_eof() {
break
}
self.set_video_ts(vtsc.into());
return false
}
}
if cpu.is_halt() {
vtsc = execute_halted_state_until_eof(vtsc, cpu);
break;
}
}
self.set_video_ts(vtsc.into());
self.bus_device_mut().update_timestamp(vtsc.vts.into());
true
}
fn ula_execute_single_step<C: Cpu, F>(
&mut self,
cpu: &mut C,
debug: Option<F>
) -> Result<(),()>
where F: FnOnce(CpuDebug),
{
let mut vtsc = self.ensure_next_frame_vtsc();
let res = cpu.execute_next(self, &mut vtsc, debug);
**vtsc = Self::ula_check_halt(vtsc.into(), cpu);
self.set_video_ts(vtsc.into());
self.bus_device_mut().update_timestamp(vtsc.vts.into());
res
}
fn ula_execute_instruction<C: Cpu>(
&mut self,
cpu: &mut C,
code: u8
) -> Result<(), ()>
{
const DEBUG: Option<CpuDebugFn> = None;
let mut vtsc = self.ensure_next_frame_vtsc();
let res = cpu.execute_instruction(self, &mut vtsc, DEBUG, code);
**vtsc = Self::ula_check_halt(vtsc.into(), cpu);
self.set_video_ts(vtsc.into());
self.bus_device_mut().update_timestamp(vtsc.vts.into());
res
}
}
pub fn execute_halted_state_until_eof<V, T, C>(
mut vtsc: VFrameTsCounter<V, T>,
cpu: &mut C
) -> VFrameTsCounter<V, T>
where V: VideoFrame,
T: MemoryContention,
C: Cpu
{
debug_assert_eq!(0, V::HTS_COUNT % M1_CYCLE_TS as Ts);
if vtsc.vc < 0 || vtsc.vc > V::VSL_COUNT || !vtsc.is_normalized() {
panic!("halt: a timestamp must be within the video frame range and normalized");
}
let mut r_incr: i32 = 0;
if vtsc.is_contended_address(cpu.get_pc()) && vtsc.vc < V::VSL_PIXELS.end {
let VideoTs { mut vc, mut hc } = **vtsc; if vc < V::VSL_PIXELS.start {
let hc_end = V::HTS_RANGE.end + (hc - V::HTS_RANGE.end).rem_euclid(M1_CYCLE_TS as Ts);
vc += 1;
r_incr = (i32::from(V::VSL_PIXELS.start - vc) * V::HTS_COUNT as i32 +
i32::from(hc_end - hc)) / M1_CYCLE_TS as i32;
hc = hc_end - V::HTS_COUNT;
vc = V::VSL_PIXELS.start;
}
while vc < V::VSL_PIXELS.end {
let hc0 = hc - V::HTS_COUNT;
let r_incr0 = r_incr;
while hc < V::HTS_RANGE.end {
hc = V::contention(hc) + M1_CYCLE_TS as Ts;
r_incr += 1;
}
vc += 1;
hc -= V::HTS_COUNT;
if (r_incr - r_incr0) * (M1_CYCLE_TS as i32) < (hc - hc0) as i32 {
break; }
}
if vc < V::VSL_PIXELS.end {
let mut r_line = 0;
while hc < V::HTS_RANGE.end {
hc = V::contention(hc) + M1_CYCLE_TS as Ts;
r_line += 1;
}
hc -= V::HTS_COUNT;
r_incr += i32::from(V::VSL_PIXELS.end - vc) * r_line;
}
vtsc.vc = V::VSL_PIXELS.end;
vtsc.hc = hc;
}
let vc = V::VSL_COUNT;
let hc = vtsc.hc.rem_euclid(M1_CYCLE_TS as Ts) - M1_CYCLE_TS as Ts;
r_incr += (
(i32::from(vc) - i32::from(vtsc.vc)) * V::HTS_COUNT as i32 +
(i32::from(hc) - i32::from(vtsc.hc))
) / M1_CYCLE_TS as i32;
if r_incr > 0 {
vtsc.hc = hc;
vtsc.vc = vc;
cpu.add_r(r_incr);
}
vtsc
}