// // Z80.cpp // Clock Signal // // Created by Thomas Harte on 30/12/2017. // Copyright 2017 Thomas Harte. All rights reserved. // #include "Z80.hpp" #include "Kernel.hpp" using namespace Analyser::Static::Z80; namespace { using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly; class Accessor { public: Accessor( const std::vector &memory, const std::function &address_mapper, uint16_t address ) : memory_(memory), address_mapper_(address_mapper), address_(address) {} uint8_t byte() { std::size_t mapped_address = address_mapper_(address_); ++address_; if(mapped_address >= memory_.size()) { overrun_ = true; return 0xff; } return memory_[mapped_address]; } uint16_t word() { uint8_t low = byte(); uint8_t high = byte(); return uint16_t(low | (high << 8)); } bool overrun() const { return overrun_; } bool at_end() const { std::size_t mapped_address = address_mapper_(address_); return mapped_address >= memory_.size(); } uint16_t address() const { return address_; } private: const std::vector &memory_; const std::function &address_mapper_; uint16_t address_; bool overrun_ = false; }; constexpr uint8_t x(uint8_t v) { return v >> 6; } constexpr uint8_t y(uint8_t v) { return (v >> 3) & 7; } constexpr uint8_t q(uint8_t v) { return (v >> 3) & 1; } constexpr uint8_t p(uint8_t v) { return (v >> 4) & 3; } constexpr uint8_t z(uint8_t v) { return v & 7; } Instruction::Condition condition_table[] = { Instruction::Condition::NZ, Instruction::Condition::Z, Instruction::Condition::NC, Instruction::Condition::C, Instruction::Condition::PO, Instruction::Condition::PE, Instruction::Condition::P, Instruction::Condition::M }; Instruction::Location register_pair_table[] = { Instruction::Location::BC, Instruction::Location::DE, Instruction::Location::HL, Instruction::Location::SP }; Instruction::Location register_pair_table2[] = { Instruction::Location::BC, Instruction::Location::DE, Instruction::Location::HL, Instruction::Location::AF }; Instruction::Location RegisterTableEntry( const int offset, Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset ) { constexpr Instruction::Location register_table[] = { Instruction::Location::B, Instruction::Location::C, Instruction::Location::D, Instruction::Location::E, Instruction::Location::H, Instruction::Location::L, Instruction::Location::HL_Indirect, Instruction::Location::A }; const Instruction::Location location = register_table[offset]; if(location == Instruction::Location::HL_Indirect && needs_indirect_offset) { instruction.offset = accessor.byte() - 128; } return location; } constexpr Instruction::Operation alu_table[] = { Instruction::Operation::ADD, Instruction::Operation::ADC, Instruction::Operation::SUB, Instruction::Operation::SBC, Instruction::Operation::AND, Instruction::Operation::XOR, Instruction::Operation::OR, Instruction::Operation::CP }; constexpr Instruction::Operation rotation_table[] = { Instruction::Operation::RLC, Instruction::Operation::RRC, Instruction::Operation::RL, Instruction::Operation::RR, Instruction::Operation::SLA, Instruction::Operation::SRA, Instruction::Operation::SLL, Instruction::Operation::SRL }; constexpr Instruction::Operation block_table[][4] = { { Instruction::Operation::LDI, Instruction::Operation::CPI, Instruction::Operation::INI, Instruction::Operation::OUTI }, { Instruction::Operation::LDD, Instruction::Operation::CPD, Instruction::Operation::IND, Instruction::Operation::OUTD }, { Instruction::Operation::LDIR, Instruction::Operation::CPIR, Instruction::Operation::INIR, Instruction::Operation::OTIR }, { Instruction::Operation::LDDR, Instruction::Operation::CPDR, Instruction::Operation::INDR, Instruction::Operation::OTDR }, }; void DisassembleCBPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) { const uint8_t operation = accessor.byte(); if(!x(operation)) { instruction.operation = rotation_table[y(operation)]; instruction.source = instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset); } else { instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset); instruction.source = Instruction::Location::Operand; instruction.operand = y(operation); switch(x(operation)) { case 1: instruction.operation = Instruction::Operation::BIT; break; case 2: instruction.operation = Instruction::Operation::RES; break; case 3: instruction.operation = Instruction::Operation::SET; break; } } } void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) { const uint8_t operation = accessor.byte(); switch(x(operation)) { default: instruction.operation = Instruction::Operation::Invalid; break; case 2: if(z(operation) < 4 && y(operation) >= 4) { instruction.operation = block_table[y(operation)-4][z(operation)]; } else { instruction.operation = Instruction::Operation::Invalid; } break; case 3: switch(z(operation)) { case 0: instruction.operation = Instruction::Operation::IN; instruction.source = Instruction::Location::BC_Indirect; if(y(operation) == 6) { instruction.destination = Instruction::Location::None; } else { instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset); } break; case 1: instruction.operation = Instruction::Operation::OUT; instruction.destination = Instruction::Location::BC_Indirect; if(y(operation) == 6) { instruction.source = Instruction::Location::None; } else { instruction.source = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset); } break; case 2: instruction.operation = (y(operation)&1) ? Instruction::Operation::ADC : Instruction::Operation::SBC; instruction.destination = Instruction::Location::HL; instruction.source = register_pair_table[y(operation) >> 1]; break; case 3: instruction.operation = Instruction::Operation::LD; if(q(operation)) { instruction.destination = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset); instruction.source = Instruction::Location::Operand_Indirect; } else { instruction.destination = Instruction::Location::Operand_Indirect; instruction.source = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset); } instruction.operand = accessor.word(); break; case 4: instruction.operation = Instruction::Operation::NEG; break; case 5: instruction.operation = y(operation) == 1 ? Instruction::Operation::RETI : Instruction::Operation::RETN; break; case 6: instruction.operation = Instruction::Operation::IM; instruction.source = Instruction::Location::Operand; switch(y(operation)&3) { case 0: instruction.operand = 0; break; case 1: instruction.operand = 0; break; case 2: instruction.operand = 1; break; case 3: instruction.operand = 2; break; } break; case 7: switch(y(operation)) { case 0: instruction.operation = Instruction::Operation::LD; instruction.destination = Instruction::Location::I; instruction.source = Instruction::Location::A; break; case 1: instruction.operation = Instruction::Operation::LD; instruction.destination = Instruction::Location::R; instruction.source = Instruction::Location::A; break; case 2: instruction.operation = Instruction::Operation::LD; instruction.destination = Instruction::Location::A; instruction.source = Instruction::Location::I; break; case 3: instruction.operation = Instruction::Operation::LD; instruction.destination = Instruction::Location::A; instruction.source = Instruction::Location::R; break; case 4: instruction.operation = Instruction::Operation::RRD; break; case 5: instruction.operation = Instruction::Operation::RLD; break; default: instruction.operation = Instruction::Operation::NOP; break; } break; } break; } } void DisassembleMainPage(Accessor &accessor, Instruction &instruction) { bool needs_indirect_offset = false; enum HLSubstitution { None, IX, IY } hl_substitution = None; while(true) { const uint8_t operation = accessor.byte(); switch(x(operation)) { case 0: switch(z(operation)) { case 0: switch(y(operation)) { case 0: instruction.operation = Instruction::Operation::NOP; break; case 1: instruction.operation = Instruction::Operation::EXAFAFd; break; case 2: instruction.operation = Instruction::Operation::DJNZ; instruction.operand = accessor.byte() - 128; break; default: instruction.operation = Instruction::Operation::JR; instruction.operand = accessor.byte() - 128; if(y(operation) >= 4) instruction.condition = condition_table[y(operation) - 4]; break; } break; case 1: if(y(operation)&1) { instruction.operation = Instruction::Operation::ADD; instruction.destination = Instruction::Location::HL; instruction.source = register_pair_table[y(operation) >> 1]; } else { instruction.operation = Instruction::Operation::LD; instruction.destination = register_pair_table[y(operation) >> 1]; instruction.source = Instruction::Location::Operand; instruction.operand = accessor.word(); } break; case 2: switch(y(operation)) { case 0: instruction.operation = Instruction::Operation::LD; instruction.destination = Instruction::Location::BC_Indirect; instruction.source = Instruction::Location::A; break; case 1: instruction.operation = Instruction::Operation::LD; instruction.destination = Instruction::Location::A; instruction.source = Instruction::Location::BC_Indirect; break; case 2: instruction.operation = Instruction::Operation::LD; instruction.destination = Instruction::Location::DE_Indirect; instruction.source = Instruction::Location::A; break; case 3: instruction.operation = Instruction::Operation::LD; instruction.destination = Instruction::Location::A; instruction.source = Instruction::Location::DE_Indirect; break; case 4: instruction.operation = Instruction::Operation::LD; instruction.destination = Instruction::Location::Operand_Indirect; instruction.source = Instruction::Location::HL; break; case 5: instruction.operation = Instruction::Operation::LD; instruction.destination = Instruction::Location::HL; instruction.source = Instruction::Location::Operand_Indirect; break; case 6: instruction.operation = Instruction::Operation::LD; instruction.destination = Instruction::Location::Operand_Indirect; instruction.source = Instruction::Location::A; break; case 7: instruction.operation = Instruction::Operation::LD; instruction.destination = Instruction::Location::A; instruction.source = Instruction::Location::Operand_Indirect; break; } if(y(operation) > 3) { instruction.operand = accessor.word(); } break; case 3: if(y(operation)&1) { instruction.operation = Instruction::Operation::DEC; } else { instruction.operation = Instruction::Operation::INC; } instruction.source = instruction.destination = register_pair_table[y(operation) >> 1]; break; case 4: instruction.operation = Instruction::Operation::INC; instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset); break; case 5: instruction.operation = Instruction::Operation::DEC; instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset); break; case 6: instruction.operation = Instruction::Operation::LD; instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset); instruction.source = Instruction::Location::Operand; instruction.operand = accessor.byte(); break; case 7: switch(y(operation)) { case 0: instruction.operation = Instruction::Operation::RLCA; break; case 1: instruction.operation = Instruction::Operation::RRCA; break; case 2: instruction.operation = Instruction::Operation::RLA; break; case 3: instruction.operation = Instruction::Operation::RRA; break; case 4: instruction.operation = Instruction::Operation::DAA; break; case 5: instruction.operation = Instruction::Operation::CPL; break; case 6: instruction.operation = Instruction::Operation::SCF; break; case 7: instruction.operation = Instruction::Operation::CCF; break; } break; } break; case 1: if(y(operation) == 6 && z(operation) == 6) { instruction.operation = Instruction::Operation::HALT; } else { instruction.operation = Instruction::Operation::LD; instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset); instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset); } break; case 2: instruction.operation = alu_table[y(operation)]; instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset); instruction.destination = Instruction::Location::A; break; case 3: switch(z(operation)) { case 0: instruction.operation = Instruction::Operation::RET; instruction.condition = condition_table[y(operation)]; break; case 1: switch(y(operation)) { default: instruction.operation = Instruction::Operation::POP; instruction.source = register_pair_table2[y(operation) >> 1]; break; case 1: instruction.operation = Instruction::Operation::RET; break; case 3: instruction.operation = Instruction::Operation::EXX; break; case 5: instruction.operation = Instruction::Operation::JP; instruction.source = Instruction::Location::HL; break; case 7: instruction.operation = Instruction::Operation::LD; instruction.destination = Instruction::Location::SP; instruction.source = Instruction::Location::HL; break; } break; case 2: instruction.operation = Instruction::Operation::JP; instruction.condition = condition_table[y(operation)]; instruction.operand = accessor.word(); break; case 3: switch(y(operation)) { case 0: instruction.operation = Instruction::Operation::JP; instruction.source = Instruction::Location::Operand; instruction.operand = accessor.word(); break; case 1: DisassembleCBPage(accessor, instruction, needs_indirect_offset); break; case 2: instruction.operation = Instruction::Operation::OUT; instruction.source = Instruction::Location::A; instruction.destination = Instruction::Location::Operand_Indirect; instruction.operand = accessor.byte(); break; case 3: instruction.operation = Instruction::Operation::IN; instruction.destination = Instruction::Location::A; instruction.source = Instruction::Location::Operand_Indirect; instruction.operand = accessor.byte(); break; case 4: instruction.operation = Instruction::Operation::EX; instruction.destination = Instruction::Location::SP_Indirect; instruction.source = Instruction::Location::HL; break; case 5: instruction.operation = Instruction::Operation::EX; instruction.destination = Instruction::Location::DE; instruction.source = Instruction::Location::HL; break; case 6: instruction.operation = Instruction::Operation::DI; break; case 7: instruction.operation = Instruction::Operation::EI; break; } break; case 4: instruction.operation = Instruction::Operation::CALL; instruction.source = Instruction::Location::Operand_Indirect; instruction.operand = accessor.word(); instruction.condition = condition_table[y(operation)]; break; case 5: switch(y(operation)) { default: instruction.operation = Instruction::Operation::PUSH; instruction.source = register_pair_table2[y(operation) >> 1]; break; case 1: instruction.operation = Instruction::Operation::CALL; instruction.source = Instruction::Location::Operand; instruction.operand = accessor.word(); break; case 3: needs_indirect_offset = true; hl_substitution = IX; continue; // i.e. repeat loop. case 5: DisassembleEDPage(accessor, instruction, needs_indirect_offset); break; case 7: needs_indirect_offset = true; hl_substitution = IY; continue; // i.e. repeat loop. } break; case 6: instruction.operation = alu_table[y(operation)]; instruction.source = Instruction::Location::Operand; instruction.destination = Instruction::Location::A; instruction.operand = accessor.byte(); break; case 7: instruction.operation = Instruction::Operation::RST; instruction.source = Instruction::Location::Operand; instruction.operand = y(operation) << 3; break; } break; } // This while(true) isn't an infinite loop for everything except those paths that opt in // via continue. break; } // Perform IX/IY substitution for HL, if applicable. if(hl_substitution != None) { // EX DE, HL is not affected. if(instruction.operation == Instruction::Operation::EX) return; // If an (HL) is involved, switch it for IX+d or IY+d. if( instruction.source == Instruction::Location::HL_Indirect || instruction.destination == Instruction::Location::HL_Indirect) { if(instruction.source == Instruction::Location::HL_Indirect) { instruction.source = hl_substitution == IX ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset; } if(instruction.destination == Instruction::Location::HL_Indirect) { instruction.destination = hl_substitution == IX ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset; } return; } // Otherwise, switch either of H or L for I[X/Y]h and I[X/Y]l. if(instruction.source == Instruction::Location::H) { instruction.source = (hl_substitution == IX) ? Instruction::Location::IXh : Instruction::Location::IYh; } if(instruction.source == Instruction::Location::L) { instruction.source = (hl_substitution == IX) ? Instruction::Location::IXl : Instruction::Location::IYl; } if(instruction.destination == Instruction::Location::H) { instruction.destination = (hl_substitution == IX) ? Instruction::Location::IXh : Instruction::Location::IYh; } if(instruction.destination == Instruction::Location::L) { instruction.destination = (hl_substitution == IX) ? Instruction::Location::IXl : Instruction::Location::IYl; } } } struct Z80Disassembler { static void AddToDisassembly( PartialDisassembly &disassembly, const std::vector &memory, const std::function &address_mapper, const uint16_t entry_point ) { disassembly.disassembly.internal_calls.insert(entry_point); Accessor accessor(memory, address_mapper, entry_point); while(!accessor.at_end()) { Instruction instruction; instruction.address = accessor.address(); DisassembleMainPage(accessor, instruction); // If any memory access was invalid, end disassembly. if(accessor.overrun()) return; // Store the instruction away. disassembly.disassembly.instructions_by_address[instruction.address] = instruction; // Update access tables. int access_type = ((instruction.source == Instruction::Location::Operand_Indirect) ? 1 : 0) | ((instruction.destination == Instruction::Location::Operand_Indirect) ? 2 : 0); uint16_t address = uint16_t(instruction.operand); bool is_internal = address_mapper(address) < memory.size(); switch(access_type) { default: break; case 1: if(is_internal) { disassembly.disassembly.internal_loads.insert(address); } else { disassembly.disassembly.external_loads.insert(address); } break; case 2: if(is_internal) { disassembly.disassembly.internal_stores.insert(address); } else { disassembly.disassembly.external_stores.insert(address); } break; case 3: if(is_internal) { disassembly.disassembly.internal_modifies.insert(address); } else { disassembly.disassembly.internal_modifies.insert(address); } break; } // Add any (potentially) newly-discovered entry point. if( instruction.operation == Instruction::Operation::JP || instruction.operation == Instruction::Operation::JR || instruction.operation == Instruction::Operation::CALL || instruction.operation == Instruction::Operation::RST) { disassembly.remaining_entry_points.push_back(uint16_t(instruction.operand)); } // This is it if: an unconditional RET, RETI, RETN, JP or JR is found. switch(instruction.operation) { default: break; case Instruction::Operation::RET: case Instruction::Operation::RETI: case Instruction::Operation::RETN: case Instruction::Operation::JP: case Instruction::Operation::JR: if(instruction.condition == Instruction::Condition::None) { disassembly.implicit_entry_points.push_back(accessor.address()); return; } } } } }; } // end of anonymous namespace Disassembly Analyser::Static::Z80::Disassemble( const std::vector &memory, const std::function &address_mapper, std::vector entry_points, Approach approach) { return Analyser::Static::Disassembly::Disassemble( memory, address_mapper, entry_points, approach == Approach::Exhaustive ); }