hg80 1.0.0

Z80 and Z80N CPU core, stepped one clock edge at a time
Documentation
// Portions of this file are derived from the T80 Z80-compatible microprocessor core,
// Copyright (c) 2001-2002 Daniel Wallner, and from the T80N modifications made for the
// ZX Spectrum Next Project, Copyright 2020 Fabio Belavenuto, Victor Trucco, Charlie Ingley,
// Garry Lancaster, ACX. Redistributed under the three-clause BSD licence reproduced in NOTICE.

//! The extended table behind the `ED` prefix, and the index-register prefixes.

use super::z80n::extended_z80n;
use super::{
    AddressSource, AluOp, Context, Decoded, InterruptMode, SpecialLoad, Z80nCommand, destination,
    pair, pair_high, pair_low,
};
use crate::control::{bus, step};

#[allow(clippy::too_many_lines)]
pub(super) fn extended(context: Context, out: &mut Decoded) {
    let ir = context.ir;
    let cycle = context.machine_cycle.number();

    match ir {
        0x57 => {
            out.special_ld = SpecialLoad::AccumulatorFromVector;
            out.t_states = 5;
        }
        0x5F => {
            out.special_ld = SpecialLoad::AccumulatorFromRefresh;
            out.t_states = 5;
        }
        0x47 => {
            out.special_ld = SpecialLoad::VectorFromAccumulator;
            out.t_states = 5;
        }
        0x4F => {
            out.special_ld = SpecialLoad::RefreshFromAccumulator;
            out.t_states = 5;
        }
        0x4B | 0x5B | 0x6B | 0x7B => {
            out.machine_cycles = 5;
            match cycle {
                2 => {
                    out.set_inc_pc();
                    out.set_ldz();
                }
                3 => {
                    out.set_addr_to = AddressSource::Latch;
                    out.set_inc_pc();
                    out.set_ldw();
                }
                4 => {
                    out.set_read_to_reg();
                    out.set_bus_a_to = pair_low(pair(ir));
                    out.set_inc_wz();
                    out.set_addr_to = AddressSource::Latch;
                }
                5 => {
                    out.set_read_to_reg();
                    out.set_bus_a_to = pair_high(pair(ir));
                }
                _ => {}
            }
        }
        0x43 | 0x53 | 0x63 | 0x73 => {
            out.machine_cycles = 5;
            match cycle {
                2 => {
                    out.set_inc_pc();
                    out.set_ldz();
                }
                3 => {
                    out.set_addr_to = AddressSource::Latch;
                    out.set_inc_pc();
                    out.set_ldw();
                    out.set_bus_b_to = pair_low(pair(ir));
                }
                4 => {
                    out.set_inc_wz();
                    out.set_addr_to = AddressSource::Latch;
                    out.set_write();
                    out.set_bus_b_to = pair_high(pair(ir));
                }
                5 => out.set_write(),
                _ => {}
            }
        }
        0xA0 | 0xA8 | 0xB0 | 0xB8 => block_transfer(context, out),
        0xA1 | 0xA9 | 0xB1 | 0xB9 => {
            out.machine_cycles = 4;
            match cycle {
                1 => {
                    out.set_addr_to = AddressSource::IndexOrHl;
                    out.inc_dec_16 = step::down(step::BC);
                }
                2 => {
                    out.set_bus_b_to = bus::DATA_LATCH;
                    out.set_bus_a_to = bus::ACCUMULATOR;
                    out.alu_op = AluOp::Cp;
                    out.set_save_alu();
                    out.set_preserve_c();
                    out.inc_dec_16 = if ir & 0x08 == 0 {
                        step::up(step::HL)
                    } else {
                        step::down(step::HL)
                    };
                    out.set_no_pc();
                }
                3 => {
                    out.set_no_read();
                    out.set_i_bc();
                    out.t_states = 5;
                    out.set_no_pc();
                }
                4 => {
                    out.set_no_read();
                    out.t_states = 5;
                }
                _ => {}
            }
        }
        0x44 | 0x4C | 0x54 | 0x5C | 0x64 | 0x6C | 0x74 | 0x7C => {
            out.alu_op = AluOp::Sub;
            out.set_bus_b_to = bus::ACCUMULATOR;
            out.set_bus_a_to = bus::ONE;
            out.set_read_to_acc();
            out.set_save_alu();
        }
        0x46 | 0x4E | 0x66 | 0x6E => out.interrupt_mode = Some(InterruptMode::Mode0),
        0x56 | 0x76 => out.interrupt_mode = Some(InterruptMode::Mode1),
        0x5E | 0x7E => out.interrupt_mode = Some(InterruptMode::Mode2),
        0x4A | 0x5A | 0x6A | 0x7A => wide_arithmetic(context, out, AluOp::Adc),
        0x42 | 0x52 | 0x62 | 0x72 => wide_arithmetic(context, out, AluOp::Sbc),
        0x6F => nibble_rotate(context, out, AluOp::Rld),
        0x67 => nibble_rotate(context, out, AluOp::Rrd),
        0x4D => {
            out.machine_cycles = 3;
            match cycle {
                1 => out.set_addr_to = AddressSource::Sp,
                2 => {
                    out.inc_dec_16 = step::up(step::STACK);
                    out.set_addr_to = AddressSource::Sp;
                    out.set_ldz();
                }
                3 => {
                    out.set_jump();
                    out.inc_dec_16 = step::up(step::STACK);
                    out.set_i_retn();
                }
                _ => {}
            }
        }
        0x45 | 0x55 | 0x5D | 0x65 | 0x6D | 0x75 | 0x7D => {
            out.machine_cycles = 3;
            match cycle {
                1 => out.set_addr_to = AddressSource::Sp,
                2 => {
                    out.inc_dec_16 = step::up(step::STACK);
                    out.set_addr_to = AddressSource::Sp;
                    out.set_ldz();
                    if context.z80n_enabled {
                        out.z80n_command = Some(Z80nCommand::ReturnFromNmiLow);
                    }
                }
                3 => {
                    out.set_jump();
                    out.inc_dec_16 = step::up(step::STACK);
                    out.set_i_retn();
                    if context.z80n_enabled {
                        out.z80n_command = Some(Z80nCommand::ReturnFromNmiHigh);
                    }
                }
                _ => {}
            }
        }
        0x40 | 0x48 | 0x50 | 0x58 | 0x60 | 0x68 | 0x70 | 0x78 => {
            out.machine_cycles = 2;
            match cycle {
                1 => out.set_addr_to = AddressSource::Bc,
                2 => {
                    out.set_iorq();
                    if destination(ir) != bus::DATA_LATCH {
                        out.set_read_to_reg();
                        out.set_bus_a_to = destination(ir);
                    }
                    out.set_i_inrc();
                }
                _ => {}
            }
        }
        0x41 | 0x49 | 0x51 | 0x59 | 0x61 | 0x69 | 0x71 | 0x79 => {
            out.machine_cycles = 2;
            match cycle {
                1 => {
                    out.set_addr_to = AddressSource::Bc;
                    out.set_bus_b_to = destination(ir);
                    if destination(ir) == bus::DATA_LATCH {
                        out.set_bus_b_to |= bus::STACK_LOW;
                    }
                }
                2 => {
                    out.set_write();
                    out.set_iorq();
                }
                _ => {}
            }
        }
        0xA2 | 0xAA | 0xB2 | 0xBA => {
            out.machine_cycles = 4;
            match cycle {
                1 => {
                    out.t_states = 5;
                    out.set_addr_to = AddressSource::Bc;
                    out.set_bus_b_to = bus::ONE;
                    out.set_bus_a_to = bus::B;
                    out.set_read_to_reg();
                    out.set_save_alu();
                    out.alu_op = AluOp::Sub;
                    out.inc_dec_16 = ir & 0x08;
                }
                2 => {
                    out.set_iorq();
                    out.set_bus_b_to = bus::DATA_LATCH;
                    out.set_addr_to = AddressSource::IndexOrHl;
                }
                3 => {
                    out.inc_dec_16 = if ir & 0x08 == 0 {
                        step::up(step::HL)
                    } else {
                        step::down(step::HL)
                    };
                    out.set_write();
                    out.set_i_btr();
                    out.set_no_pc();
                }
                4 => {
                    out.set_no_read();
                    out.t_states = 5;
                }
                _ => {}
            }
        }
        0xA3 | 0xAB | 0xB3 | 0xBB => port_block_out(context, out, false),
        _ => extended_z80n(context, out),
    }
}

pub(super) fn block_transfer(context: Context, out: &mut Decoded) {
    let ir = context.ir;
    let skips_matching = matches!(ir, 0xA4 | 0xB4);

    out.machine_cycles = 4;
    match context.machine_cycle.number() {
        1 => {
            out.set_addr_to = AddressSource::IndexOrHl;
            out.inc_dec_16 = step::down(step::BC);
        }
        2 => {
            out.set_bus_b_to = bus::DATA_LATCH;
            out.set_bus_a_to = bus::ACCUMULATOR;
            out.alu_op = AluOp::Add;
            out.set_addr_to = AddressSource::De;
            out.inc_dec_16 = if ir & 0x08 == 0 {
                step::up(step::HL)
            } else {
                step::down(step::HL)
            };
        }
        3 => {
            out.set_i_bt();
            out.t_states = 5;
            out.set_write_to(!skips_matching || context.accumulator != context.data);
            out.inc_dec_16 = if ir & 0x08 == 0 {
                step::up(step::DE)
            } else {
                step::down(step::DE)
            };
            out.set_no_pc();
        }
        4 => {
            out.set_no_read();
            out.t_states = 5;
        }
        _ => {}
    }
}

fn wide_arithmetic(context: Context, out: &mut Decoded, op: AluOp) {
    let dpair = pair(context.ir);

    out.machine_cycles = 3;
    match context.machine_cycle.number() {
        1 => out.set_no_pc(),
        2 => {
            out.set_no_read();
            out.alu_op = op;
            out.set_read_to_reg();
            out.set_save_alu();
            out.set_bus_a_to = bus::L;
            out.set_bus_b_to = pair_low(dpair);
            out.t_states = 4;
            out.set_no_pc();
        }
        3 => {
            out.set_no_read();
            out.set_read_to_reg();
            out.set_save_alu();
            out.alu_op = op;
            out.set_bus_a_to = bus::H;
            out.set_bus_b_to = pair_high(dpair);
        }
        _ => {}
    }
}

fn nibble_rotate(context: Context, out: &mut Decoded, op: AluOp) {
    out.machine_cycles = 4;
    match context.machine_cycle.number() {
        1 => out.set_addr_to = AddressSource::IndexOrHl,
        2 => {
            out.set_read_to_reg();
            out.set_bus_b_to = bus::DATA_LATCH;
            out.set_bus_a_to = bus::ACCUMULATOR;
            out.alu_op = op;
            out.set_save_alu();
            out.set_no_pc();
        }
        3 => {
            out.t_states = 4;
            if matches!(op, AluOp::Rld) {
                out.set_i_rld();
            } else {
                out.set_i_rrd();
            }
            out.set_no_read();
            out.set_addr_to = AddressSource::IndexOrHl;
        }
        4 => out.set_write(),
        _ => {}
    }
}

pub(super) fn port_block_out(context: Context, out: &mut Decoded, single_step: bool) {
    let ir = context.ir;

    out.machine_cycles = if single_step { 3 } else { 4 };
    match context.machine_cycle.number() {
        1 => {
            out.t_states = 5;
            out.set_addr_to = AddressSource::IndexOrHl;
            out.set_bus_b_to = bus::ONE;
            out.set_bus_a_to = bus::B;
            if !single_step {
                out.set_read_to_reg();
                out.set_save_alu();
                out.alu_op = AluOp::Sub;
            }
        }
        2 => {
            out.set_bus_b_to = bus::DATA_LATCH;
            out.set_addr_to = AddressSource::Bc;
            out.inc_dec_16 = ir & 0x08;
        }
        3 => {
            out.inc_dec_16 = if ir & 0x08 == 0 {
                step::up(step::HL)
            } else {
                step::down(step::HL)
            };
            out.set_iorq();
            out.set_write();
            out.set_i_btr();
        }
        4 => {
            out.set_no_read();
            out.t_states = 5;
        }
        _ => {}
    }
}