diff --git a/Cargo.lock b/Cargo.lock index ded81d3..2cd1430 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,7 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] -name = "iz80" -version = "0.3.6" +name = "ez80" +version = "0.4.2" diff --git a/Cargo.toml b/Cargo.toml index 86eadc0..a4041a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [package] -name = "iz80" -version = "0.3.6" -authors = ["Ivan Izaguirre "] +name = "ez80" +version = "0.4.2" +authors = ["Tom Morton ", "Ivan Izaguirre "] edition = "2018" license = "BSD-3-Clause" -description = "Z80 and 8080 emulator" -keywords = ["Z80", "8080", "CPM", "emulator"] -homepage = "https://github.com/ivanizag/iz80" -repository = "https://github.com/ivanizag/iz80" +description = "Zilog eZ80, Z80 and Intel 8080 emulator" +keywords = ["eZ80", "Z80", "8080", "CPM", "Agon Light", "emulator"] +homepage = "https://github.com/tomm/ez80" +repository = "https://github.com/tomm/ez80" readme = "README.md" [dependencies] diff --git a/LICENSE b/LICENSE index c2b3ac4..dda0b4d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ BSD 3-Clause License +Copyright (c) 2023, Tom Morton Copyright (c) 2021, Iván Izaguirre All rights reserved. diff --git a/README.md b/README.md index 4eea163..170f883 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ -# iz80 +# ez80 -[![Build Status](https://github.com/ivanizag/iz80/workflows/Build/badge.svg)](https://github.com/ivanizag/iz80/actions?workflow=Build) -[![Crates](https://img.shields.io/crates/v/iz80.svg)](https://crates.io/crates/iz80) -[![Documentation](https://docs.rs/iz80/badge.svg)](https://docs.rs/iz80) +This is an extension of [iz80](https://github.com/ivanizag/iz80) to support the eZ80 CPU. -Zilog Z80 and Intel 8080 emulator library for RUST. It passes all the tests of the ZEXALL suite. No cycle emulation accuracy, runs as fast as it can. +It passes all the tests of the ZEXALL suite, but the eZ80 mode is more experimental. It +is used by the [Agon Light Emulator](https://github.com/astralaster/agon-light-emulator), +via the [Agon CPU Emulator](https://github.com/tomm/agon-cpu-emulator), which emulates +the CPU and CPU-connected peripherals of the Agon Light. To run the ZEXALL test suite for Zilog Z80: diff --git a/src/bin/cpuville.rs b/src/bin/cpuville.rs index f0e6f31..d89adb2 100644 --- a/src/bin/cpuville.rs +++ b/src/bin/cpuville.rs @@ -10,8 +10,8 @@ use std::sync::mpsc::TryRecvError; use std::thread; use std::time::Duration; -use iz80::Cpu; -use iz80::Machine; +use ez80::Cpu; +use ez80::Machine; static TINY_BASIC: &[u8] = include_bytes!("rom/tinybasic2dms.bin"); @@ -27,11 +27,11 @@ fn main() { // Load program let code = TINY_BASIC; for (i, e) in code.iter().enumerate() { - machine.poke(i as u16, *e); + machine.poke(i as u32, *e); } // Init - cpu.registers().set_pc(0x0000); + cpu.state.set_pc(0x0000); machine.in_values[3] = 1; // TX Ready loop { @@ -114,11 +114,11 @@ impl VilleMachine { } impl Machine for VilleMachine { - fn peek(&self, address: u16) -> u8 { + fn peek(&self, address: u32) -> u8 { self.mem[address as usize] } - fn poke(&mut self, address: u16, value: u8) { + fn poke(&mut self, address: u32, value: u8) { self.mem[address as usize] = value; } @@ -136,6 +136,9 @@ impl Machine for VilleMachine { self.out_port = Some(address as u8); self.out_value = value; } + + fn use_cycles(&self, _cycles: i32) { + } } diff --git a/src/bin/simplest.rs b/src/bin/simplest.rs index 9bbe640..0a48b5a 100644 --- a/src/bin/simplest.rs +++ b/src/bin/simplest.rs @@ -1,4 +1,4 @@ -use iz80::*; +use ez80::*; fn main() { // Prepare the device @@ -10,11 +10,11 @@ fn main() { // let code = include_bytes!("XXXX.rom"); let code = [0x3c, 0xc3, 0x00, 0x00]; // INC A, JP $0000 for (i, e) in code.iter().enumerate() { - machine.poke(i as u16, *e); + machine.poke(i as u32, *e); } // Run emulation - cpu.registers().set_pc(0x0000); + cpu.state.set_pc(0x0000); loop { cpu.execute_instruction(&mut machine); diff --git a/src/bin/simplest8080.rs b/src/bin/simplest8080.rs index cefdd28..1cdadd4 100644 --- a/src/bin/simplest8080.rs +++ b/src/bin/simplest8080.rs @@ -1,4 +1,4 @@ -use iz80::*; +use ez80::*; fn main() { // Prepare the device @@ -10,11 +10,11 @@ fn main() { // let code = include_bytes!("XXXX.rom"); let code = [0x3c, 0xc3, 0x00, 0x00]; // INC A, JP $0000 for (i, e) in code.iter().enumerate() { - machine.poke(i as u16, *e); + machine.poke(i as u32, *e); } // Run emulation - cpu.registers().set_pc(0x0000); + cpu.state.set_pc(0x0000); loop { cpu.execute_instruction(&mut machine); diff --git a/src/cpu.rs b/src/cpu.rs index 76e9b58..668cf9b 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -1,3 +1,4 @@ +use super::decoder_ez80::*; use super::decoder_z80::*; use super::decoder_8080::*; use super::environment::*; @@ -6,13 +7,13 @@ use super::opcode::*; use super::registers::*; use super::state::*; -const NMI_ADDRESS: u16 = 0x0066; +const NMI_ADDRESS: u32 = 0x0066; /// The Z80 cpu emulator. /// /// Executes Z80 instructions changing the cpu State and Machine pub struct Cpu { - state: State, + pub state: State, trace: bool, decoder: Box, } @@ -37,6 +38,14 @@ impl Cpu { } } + pub fn new_ez80() -> Cpu { + Cpu { + state: State::new(), + trace: false, + decoder: Box::new(DecoderEZ80::new()) + } + } + /// Returns an Intel 8080 Cpu instance pub fn new_8080() -> Cpu { let mut cpu = Cpu { @@ -58,6 +67,26 @@ impl Default for Cpu { } impl Cpu { + // Executes a single instruction + // + // Alternative to execute_instruction(), used by Fab Agon Emulator for + // sake of a little efficiency. Ignores some ez80 emulator features not + // used by FAE: reset_pending and nmi_pending + pub fn fast_execute_instruction(&mut self, sys: &mut dyn Machine) { + if self.is_halted() { + // The CPU is in HALT state. Only interrupts can execute. + return + } + + let mut env = Environment::new(&mut self.state, sys); + let opcode = self.decoder.decode(&mut env); + opcode.execute(&mut env); + env.clear_index(); + env.state.clear_sz_prefix(); + env.state.instructions_executed += 1; + env.state.reg.set8(Reg8::R, env.state.reg.get8(Reg8::R).wrapping_add(1)); + } + /// Executes a single instruction /// /// # Arguments @@ -75,7 +104,7 @@ impl Cpu { env.state.reset_pending = false; env.state.nmi_pending = false; env.state.halted = false; - env.state.reg.set_pc(0x0000); + env.state.set_pc(0x0000); env.state.reg.set8(Reg8::I, 0x00); env.state.reg.set8(Reg8::R, 0x00); env.state.reg.set_interrupts(false); @@ -88,28 +117,37 @@ impl Cpu { env.subroutine_call(NMI_ADDRESS); } - let pc = env.state.reg.pc(); + let pc = env.state.pc(); let opcode = self.decoder.decode(&mut env); if self.trace { - print!("==> {:04x}: {:20}", pc, opcode.disasm(&env)); + print!("==> {:06x}: {:20}", pc, opcode.disasm(&env).0); } opcode.execute(&mut env); env.clear_index(); + env.state.clear_sz_prefix(); + env.state.instructions_executed += 1; + env.state.reg.set8(Reg8::R, env.state.reg.get8(Reg8::R).wrapping_add(1)); if self.trace { - print!(" PC:{:04x} AF:{:04x} BC:{:04x} DE:{:04x} HL:{:04x} SP:{:04x} IX:{:04x} IY:{:04x} Flags:{:08b}", - self.state.reg.pc(), + print!(" PC:{:06x} AF:{:04x} BC:{:06x} DE:{:06x} HL:{:06x} SPS:{:04x} SPL:{:06x} IX:{:06x} IY:{:06x} MB {:02x} ADL {:01x} MADL {:01x} tick {}", + self.state.pc(), self.state.reg.get16(Reg16::AF), - self.state.reg.get16(Reg16::BC), - self.state.reg.get16(Reg16::DE), - self.state.reg.get16(Reg16::HL), + self.state.reg.get24(Reg16::BC), + self.state.reg.get24(Reg16::DE), + self.state.reg.get24(Reg16::HL), self.state.reg.get16(Reg16::SP), - self.state.reg.get16(Reg16::IX), - self.state.reg.get16(Reg16::IY), - self.state.reg.get8(Reg8::F) + self.state.reg.get24(Reg16::SP), + self.state.reg.get24(Reg16::IX), + self.state.reg.get24(Reg16::IY), + self.state.reg.mbase, + self.state.reg.adl as i32, + self.state.reg.madl as i32, + self.state.instructions_executed, ); - println!(" [{:02x} {:02x} {:02x}]", sys.peek(pc), - sys.peek(pc.wrapping_add(1)), sys.peek(pc.wrapping_add(2))); + println!(" [{:02x} {:02x} {:02x} {:02x}]", sys.peek(pc), + sys.peek(pc.wrapping_add(1)), + sys.peek(pc.wrapping_add(2)), + sys.peek(pc.wrapping_add(3))); } } @@ -122,7 +160,9 @@ impl Cpu { pub fn disasm_instruction(&mut self, sys: &mut dyn Machine) -> String { let mut env = Environment::new(&mut self.state, sys); let opcode = self.decoder.decode(&mut env); - opcode.disasm(&env) + let (asm, pc_inc) = opcode.disasm(&env); + for _ in 0..pc_inc { env.advance_pc(); } + asm } /// Activates or deactivates traces of the instruction executed and @@ -135,6 +175,11 @@ impl Cpu { self.trace = trace; } + /// Set eZ80 ADL state + pub fn set_adl(&mut self, adl: bool) { + self.state.reg.adl = adl; + } + /// Returns a Registers struct to read and write on the Z80 registers pub fn registers(&mut self) -> &mut Registers { &mut self.state.reg diff --git a/src/decoder_ez80.rs b/src/decoder_ez80.rs new file mode 100644 index 0000000..f18ddba --- /dev/null +++ b/src/decoder_ez80.rs @@ -0,0 +1,697 @@ +use super::cpu::*; +use super::opcode::*; +use super::opcode_alu::*; +use super::opcode_arith::*; +use super::opcode_io::*; +use super::opcode_bits::*; +use super::opcode_jumps::*; +use super::opcode_ld::*; +use super::operators::*; +use super::registers::*; +use super::environment::*; +use super::state::*; + +/* See + http://www.z80.info/decoding.htm + http://clrhome.org/table/ + http://z80-heaven.wikidot.com/instructions-set +*/ + +pub struct DecoderEZ80 { + no_prefix: [Option; 256], + prefix_cb: [Option; 256], + prefix_cb_indexed: [Option; 256], + prefix_ed: [Option; 256], + // prefix_dd & prefix_fd are only used for a few ez80 instructions. + // the rest of those prefixes are handled by the environment.index hack + prefix_dd: [Option; 256], + prefix_fd: [Option; 256], + has_displacement: [bool; 256], +} + +impl Decoder for DecoderEZ80 { + fn decode(&self, env: &mut Environment) -> &Opcode { + let mut b0 = env.advance_pc(); + + // Process prefixes even if reapeated + loop { + match b0 { + 0x40 => env.state.sz_prefix = SizePrefix::SIS, + 0x49 => env.state.sz_prefix = SizePrefix::LIS, + 0x52 => env.state.sz_prefix = SizePrefix::SIL, + 0x5B => env.state.sz_prefix = SizePrefix::LIL, + _ => break, + } + b0 = env.advance_pc(); + } + loop { + match b0 { + 0xdd => env.set_index(Reg16::IX), + 0xfd => env.set_index(Reg16::IY), + _ => break, + } + b0 = env.advance_pc(); + } + + let opcode = match b0 { + 0xcb => { + if env.is_alt_index() { + env.load_displacement(); + &self.prefix_cb_indexed[env.advance_pc() as usize] + } else { + &self.prefix_cb[env.advance_pc() as usize] + } + }, + 0xed => { + env.clear_index(); // With ed, the current prefix is ignored + &self.prefix_ed[env.advance_pc() as usize] + }, + // XXX hack. should put all dd, fd opcodes in this table + 0x0f | 0x1f | 0x2f | 0x07 | 0x17 | 0x27 | 0x31 | 0x37 | 0x3e | 0x3f | 0x86 + | 0x96 | 0xa6 | 0xb6 | 0x8e | 0x9e | 0xae | 0xbe if env.is_alt_index() => { + match env.get_index() { + Reg16::IX => { + env.clear_index(); + &self.prefix_dd[b0 as usize] + } + Reg16::IY => { + env.clear_index(); + &self.prefix_fd[b0 as usize] + } + _ => panic!("bug") + } + }, + _ => { + if self.has_displacement[b0 as usize] && env.is_alt_index() { + env.load_displacement(); + } + &self.no_prefix[b0 as usize] + } + }; + match opcode { + Some(o) => o, + None => { + panic!("Opcode {:02x} not defined", b0); + } + } + } +} + +impl DecoderEZ80 { + pub fn new() -> DecoderEZ80 { + + let mut decoder = DecoderEZ80 { + no_prefix: [ + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + ], + prefix_cb: [ + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + ], + prefix_cb_indexed: [ + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + ], + prefix_dd: [ + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + ], + prefix_ed: [ + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + ], + prefix_fd: [ + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + ], + has_displacement: [false; 256], + }; + decoder.load_no_prefix(); + decoder.load_prefix_cb(); + decoder.load_prefix_cb_indexed(); + decoder.load_prefix_ed(); + decoder.load_prefix_dd(); + decoder.load_prefix_fd(); + decoder.load_has_displacement(); + decoder + } + + /* Only some ez80 instructions are implemented here. Most use state.index hack */ + fn load_prefix_dd(&mut self) { + for c in 0..=255 { + let opcode = match c { + 0x07 => Some(build_ld_rr_idx_disp(Reg16::BC, Reg16::IX)), + 0x0f => Some(build_ld_idx_disp_rr(Reg16::IX, Reg16::BC)), + 0x17 => Some(build_ld_rr_idx_disp(Reg16::DE, Reg16::IX)), + 0x1f => Some(build_ld_idx_disp_rr(Reg16::IX, Reg16::DE)), + 0x27 => Some(build_ld_rr_idx_disp(Reg16::HL, Reg16::IX)), + 0x2f => Some(build_ld_idx_disp_rr(Reg16::IX, Reg16::HL)), + 0x31 => Some(build_ld_rr_idx_disp(Reg16::IY, Reg16::IX)), + 0x37 => Some(build_ld_rr_idx_disp(Reg16::IX, Reg16::IX)), + 0x3e => Some(build_ld_idx_disp_rr(Reg16::IX, Reg16::IY)), + 0x3f => Some(build_ld_idx_disp_rr(Reg16::IX, Reg16::IX)), + 0x86 => Some(build_operator_a_idx_offset(Reg16::IX, (operator_add, "ADD"))), + 0x8e => Some(build_operator_a_idx_offset(Reg16::IX, (operator_adc, "ADC"))), + 0x96 => Some(build_operator_a_idx_offset(Reg16::IX, (operator_sub, "SUB"))), + 0x9e => Some(build_operator_a_idx_offset(Reg16::IX, (operator_sbc, "SBC"))), + 0xa6 => Some(build_operator_a_idx_offset(Reg16::IX, (operator_and, "AND"))), + 0xae => Some(build_operator_a_idx_offset(Reg16::IX, (operator_xor, "XOR"))), + 0xb6 => Some(build_operator_a_idx_offset(Reg16::IX, (operator_or, "OR"))), + 0xbe => Some(build_operator_a_idx_offset(Reg16::IX, (operator_cp, "CP"))), + + _ => None + }; + self.prefix_dd[c as usize] = opcode; + } + } + + /* Only some ez80 instructions are implemented here. Most use state.index hack */ + fn load_prefix_fd(&mut self) { + for c in 0..=255 { + let opcode = match c { + 0x07 => Some(build_ld_rr_idx_disp(Reg16::BC, Reg16::IY)), + 0x0f => Some(build_ld_idx_disp_rr(Reg16::IY, Reg16::BC)), + 0x17 => Some(build_ld_rr_idx_disp(Reg16::DE, Reg16::IY)), + 0x1f => Some(build_ld_idx_disp_rr(Reg16::IY, Reg16::DE)), + 0x27 => Some(build_ld_rr_idx_disp(Reg16::HL, Reg16::IY)), + 0x2f => Some(build_ld_idx_disp_rr(Reg16::IY, Reg16::HL)), + 0x31 => Some(build_ld_rr_idx_disp(Reg16::IX, Reg16::IY)), + 0x37 => Some(build_ld_rr_idx_disp(Reg16::IY, Reg16::IY)), + 0x3e => Some(build_ld_idx_disp_rr(Reg16::IY, Reg16::IX)), + 0x3f => Some(build_ld_idx_disp_rr(Reg16::IY, Reg16::IY)), + 0x86 => Some(build_operator_a_idx_offset(Reg16::IY, (operator_add, "ADD"))), + 0x8e => Some(build_operator_a_idx_offset(Reg16::IY, (operator_adc, "ADC"))), + 0x96 => Some(build_operator_a_idx_offset(Reg16::IY, (operator_sub, "SUB"))), + 0x9e => Some(build_operator_a_idx_offset(Reg16::IY, (operator_sbc, "SBC"))), + 0xa6 => Some(build_operator_a_idx_offset(Reg16::IY, (operator_and, "ADD"))), + 0xae => Some(build_operator_a_idx_offset(Reg16::IY, (operator_xor, "XOR"))), + 0xb6 => Some(build_operator_a_idx_offset(Reg16::IY, (operator_or, "OR"))), + 0xbe => Some(build_operator_a_idx_offset(Reg16::IY, (operator_cp, "CP"))), + + _ => None + }; + self.prefix_fd[c as usize] = opcode; + } + } + + fn load_no_prefix(&mut self) { + for c in 0..=255 { + let p = DecodingHelper::parts(c); + let opcode = match p.x { + 0 => match p.z { + 0 => match p.y { // Relative jumps and assorted ops. + 0 => Some(build_nop()), // NOP + 1 => Some(build_ex_af()), // EX AF, AF' + 2 => Some(build_djnz()), // DJNZ d + 3 => Some(build_jr_unconditional()), // JR d + 4..=7 => Some(build_jr_eq(CC[p.y-4])), + _ => panic!("Unreachable") + }, + 1 => match p.q { + 0 => Some(build_ld_rr_nn(RP[p.p])), // LD rr, nn -- 16-bit load add + 1 => Some(build_add_hl_rr(RP[p.p])), // ADD HL, rr -- 16-bit add + _ => panic!("Unreachable") + }, + 2 => match p.q { + 0 => match p.p { + 0 => Some(build_ld_prr_a(Reg16::BC)), // LD (BC), A + 1 => Some(build_ld_prr_a(Reg16::DE)), // LD (DE), A + 2 => Some(build_ld_pnn_rr(Reg16::HL, true)), // LD (nn), HL + 3 => Some(build_ld_pnn_a()), // LD (nn), A + _ => panic!("Unreachable") + }, + 1 => match p.p { + 0 => Some(build_ld_a_prr(Reg16::BC)), // LD A, (BC) + 1 => Some(build_ld_a_prr(Reg16::DE)), // LD A, (DE) + 2 => Some(build_ld_rr_pnn(Reg16::HL, true)), // LD HL, (nn) + 3 => Some(build_ld_a_pnn()), // LD A, (nn) + _ => panic!("Unreachable") + } + _ => panic!("Unreachable") + }, + 3 => match p.q { + 0 => Some(build_inc_dec_rr(RP[p.p], true)), // INC rr -- 16-bit inc + 1 => Some(build_inc_dec_rr(RP[p.p], false)), // DEC rr -- 16-bit dec + _ => panic!("Unreachable") + }, + 4 => Some(build_inc_r(R[p.y])), // INC r -- 8 bit inc + 5 => Some(build_dec_r(R[p.y])), // DEC r -- 8 bit dec + 6 => Some(build_ld_r_n(R[p.y])), // LD r, n -- 8 bit load imm + 7 => match p.y { + 0..=3 => Some(build_rot_r(Reg8::A, ROT[p.y], true, false)), // rotA + 4 => Some(build_daa()), // DAA, decimal adjust A + 5 => Some(build_cpl()), // CPL, complement adjust A + 6 => Some(build_scf()), // SCF, set carry flag + 7 => Some(build_ccf()), // CCF, clear carry flag + _ => panic!("Unreachable") + }, + _ => panic!("Unreachable") + }, + 1 => match (p.z, p.y) { + (6, 6) => Some(build_halt()), // HALT, exception instead of LD (HL), (HL) + _ => Some(build_ld_r_r(R[p.y], R[p.z], false)), // LD r[y], r[z] -- 8 bit load imm + }, + 2 => Some(build_operator_a_r(R[p.z], ALU[p.y])), // alu A, r + 3 => match p.z { + 0 => Some(build_ret_eq(CC[p.y])), // RET cc + 1 => match p.q { + 0 => Some(build_pop_rr(RP2[p.p])), // POP rr + 1 => match p.p { + 0 => Some(build_ret()), // RET + 1 => Some(build_exx()), // EXX + 2 => Some(build_jp_hl()), // JP HL + 3 => Some(build_ld_sp_hl()), // LD SP, HL + _ => panic!("Unreachable") + }, + _ => panic!("Unreachable") + }, + 2 => Some(build_jp_eq(CC[p.y])), // JP cc, nn + 3 => match p.y { + 0 => Some(build_jp_unconditional()), // JP nn + 1 => None, // CB prefix + 2 => Some(build_out_n_a()), // OUT (n), A + 3 => Some(build_in_a_n()), // IN A, (n) + 4 => Some(build_ex_psp_hl()), // EX (SP), HL + 5 => Some(build_ex_de_hl()), // EX DE, HL + 6 => Some(build_conf_interrupts(false)), // DI + 7 => Some(build_conf_interrupts(true)), // EI + _ => panic!("Unreachable") + } + 4 => Some(build_call_eq(CC[p.y])), + 5 => match p.q { + 0 => Some(build_push_rr(RP2[p.p])), // PUSH rr + 1 => match p.p { + 0 => Some(build_call()), // Call nn + 1 => None, // DD prefix + 2 => None, // ED prefix + 3 => None, // FD prefix + _ => panic!("Unreachable") + }, + _ => panic!("Unreachable") + }, + 6 => Some(build_operator_a_n(ALU[p.y])), // alu A, n + 7 => Some(build_rst(p.y as u8 * 8)), // RST + _ => panic!("Unreachable") + }, + _ => panic!("Unreachable") + }; +/* + match opcode.as_ref() { + None => println!("0x{:02x} {:20}: {:?}", c, "Pending", p), + Some(o) => println!("0x{:02x} {:20}: {:?}", c, o.name, p) + } +*/ + self.no_prefix[c as usize] = opcode; + } + } + + fn load_prefix_cb(&mut self) { + for c in 0..=255 { + let p = DecodingHelper::parts(c); + let opcode = match p.x { + 0 => Some(build_rot_r(R[p.z], ROT[p.y], false, false)), // Shifts + 1 => Some(build_bit_r(p.y as u8, R[p.z])), // BIT + 2 => Some(build_set_res_r(p.y as u8, R[p.z], false)), // RES + 3 => Some(build_set_res_r(p.y as u8, R[p.z], true)), // SET + _ => panic!("Unreachable") + }; + +/* + match opcode.as_ref() { + None => println!("0x{:02x} 0x{:02x} {:15}: {:?}", 0xcb, c, "Pending", p), + Some(o) => println!("0x{:02x} 0x{:02x} {:15}: {:?}", 0xcb, c, o.name, p) + } +*/ + self.prefix_cb[c as usize] = opcode; + } + } + + fn load_prefix_cb_indexed(&mut self) { + for c in 0..=255 { + let p = DecodingHelper::parts(c); + let opcode = match p.x { + 0 => Some(build_rot_r(R[p.z], ROT[p.y], false, true)), // Shifts + 1 => Some(build_bit_r(p.y as u8, R[p.z])), // BIT + 2 => Some(build_indexed_set_res_r(p.y as u8, R[p.z], false)), // RES + 3 => Some(build_indexed_set_res_r(p.y as u8, R[p.z], true)), // SET + _ => panic!("Unreachable") + }; + +/* + match opcode.as_ref() { + None => println!("0x{:02x} 0x{:02x} {:15}: {:?}", 0xcb, c, "Pending", p), + Some(o) => println!("0x{:02x} 0x{:02x} {:15}: {:?}", 0xcb, c, o.name, p) + } +*/ + self.prefix_cb_indexed[c as usize] = opcode; + } + } + + + fn load_prefix_ed(&mut self) { + for c in 0..=255 { + let p = DecodingHelper::parts(c); + let opcode = match p.x { + 0 => match p.z { + 0 => match p.y { + 0 | 1 | 2 | 3 | 4 | 5 | 7 => Some(build_in0_r_n(R[p.y])), + _ => Some(build_noni_nop()), + } + 1 => match p.y { + 6 => Some(build_ld_rr_ind_hl(Reg16::IY)), + _ => Some(build_out0_n_r(R[p.y])), + } + 2 => match p.p { + 0 | 1 | 2 => Some(build_lea_rr_ind_offset(RP[p.p], Reg16::IX)), + 3 => Some(build_lea_rr_ind_offset(Reg16::IX, Reg16::IX)), + _ => Some(build_noni_nop()), // Invalid instruction NONI + NOP + }, + 3 => match p.p { + 0 | 1 | 2 => Some(build_lea_rr_ind_offset(RP[p.p], Reg16::IY)), + 3 => Some(build_lea_rr_ind_offset(Reg16::IY, Reg16::IY)), + _ => Some(build_noni_nop()), // Invalid instruction NONI + NOP + }, + 4 => Some(build_tst_a_r(R[p.y])), + 6 => match p.y { + 7 => Some(build_ld_ind_hl_rr(Reg16::IY)), + _ => Some(build_noni_nop()), // Invalid instruction NONI + NOP + } + 7 => match p.y { + 0 | 2 | 4 => Some(build_ld_rr_ind_hl(RP[p.p])), + 1 | 3 | 5 => Some(build_ld_ind_hl_rr(RP[p.p])), + 6 => Some(build_ld_rr_ind_hl(Reg16::IX)), + 7 => Some(build_ld_ind_hl_rr(Reg16::IX)), + _ => Some(build_noni_nop()), // Invalid instruction NONI + NOP + }, + _ => Some(build_noni_nop()), // Invalid instruction NONI + NOP + }, + 1 => match p.z { + 0 => match p.y { + 6 => Some(build_in_0_c()), // IN (C) + _ => Some(build_in_r_c(R[p.y])), // IN r, (C) + } + 1 => match p.y { + 6 => Some(build_out_c_0()), // OUT (C), 0 + _ => Some(build_out_c_r(R[p.y])), // OUT (C), r + } + 2 => match p.q { + 0 => Some(build_sbc_hl_rr(RP[p.p])), // SBC HL, rr + 1 => Some(build_adc_hl_rr(RP[p.p])), // ADC HL, rr + _ => panic!("Unreachable") + }, + 3 => match p.q { + 0 => Some(build_ld_pnn_rr(RP[p.p], false)), // LD (nn), rr -- 16 bit loading + 1 => Some(build_ld_rr_pnn(RP[p.p], false)), // LD rr, (nn) -- 16 bit loading + _ => panic!("Unreachable") + }, + 4 => match p.y { + 1 | 3 | 5 | 7 => Some(build_mlt_rr(RP[p.p])), + 2 => Some(build_lea_rr_ind_offset(Reg16::IX, Reg16::IY)), + 4 => Some(build_tst_a_n()), + 6 => Some(build_log_unimplemented("0x74: TSTIO n")), + _ => Some(build_neg()), // NEG + }, + 5 => match p.y { + 1 => Some(build_reti()), // RETI + 2 => Some(build_lea_rr_ind_offset(Reg16::IY, Reg16::IX)), + 4 => Some(build_pea(Reg16::IX)), + 5 => Some(build_ld_mb_a()), + 7 => Some(build_stmix()), + _ => Some(build_retn()) // RETN + } + 6 => match p.y { + 4 => Some(build_pea(Reg16::IY)), + 5 => Some(build_ld_a_mb()), + 6 => Some(build_log_unimplemented("SLP")), // 0x76 + 7 => Some(build_rsmix()), + _ => Some(build_im(IM[p.y])) // IM # + } + 7 => match p.y { + 0 => Some(build_ld_r_r(Reg8::I, Reg8::A, true)), // LD I, A + 1 => Some(build_ld_r_r(Reg8::R, Reg8::A, true)), // LD R, A + 2 => Some(build_ld_a_r_or_i(Reg8::I)), // LD A, I + 3 => Some(build_ld_a_r_or_i(Reg8::R)), // LD A, R + 4 => Some(build_rxd(ShiftDir::Right, "RRD")), // RRD + 5 => Some(build_rxd(ShiftDir::Left, "RLD")), // RLD + 6 => Some(build_nop()), // NOP + 7 => Some(build_nop()), // NOP + _ => panic!("Unreacheable") + }, + _ => panic!("Unreacheable") + }, + 2 => + if p.z <= 3 && p.y >= 4 { + // Table "bli" + match p.z { + 0 => Some(build_ld_block( BLI_A[p.y-4])), // Block LDxx + 1 => Some(build_cp_block( BLI_A[p.y-4])), // Block CPxx + 2 => Some(build_in_block( BLI_A[p.y-4])), // Block INxx + 3 => Some(build_out_block(BLI_A[p.y-4])), // Block OUTxx + _ => panic!("Unreacheable") + } + } else if p.z == 3 { + match p.y { + 0 => Some(build_log_unimplemented("OTIM")), // 0x83 + 1 => Some(build_log_unimplemented("OTDM")), // 0x8b + 2 => Some(build_log_unimplemented("OTIMR")), // 0x93 + 3 => Some(build_log_unimplemented("OTDMR")), // 0x9b + _ => Some(build_noni_nop()), + } + } else if p.z == 4 { + match p.y { + 0 => Some(build_log_unimplemented("INI2")), // 0x84 + 1 => Some(build_log_unimplemented("IND2")), // 0x8c + 2 => Some(build_log_unimplemented("INI2R")), // 0x94 + 3 => Some(build_log_unimplemented("IND2R")), // 0x9c + 4 => Some(build_log_unimplemented("OUTI2")), // 0xa4 + 5 => Some(build_log_unimplemented("OUTD2")), // 0xac + 6 => Some(build_log_unimplemented("OUTI2R")), // 0xb4 + 7 => Some(build_log_unimplemented("OTD2R")), // 0xbc + _ => Some(build_noni_nop()), + } + } else { + Some(build_noni_nop()) // NONI + NOP + }, + 3 => match p.z { + 2 => match p.y { + 0 => Some(build_log_unimplemented("INIRX")), // 0xc2 + 1 => Some(build_log_unimplemented("INDRX")), // 0xca + _ => Some(build_noni_nop()), // Invalid instruction NONI + NOP + } + 3 => match p.y { + 0 => Some(build_otirx_or_otdrx(true /* otirx */)), // 0xc3 + 1 => Some(build_otirx_or_otdrx(false /* otdrx */)), // 0xcb + _ => Some(build_noni_nop()), // Invalid instruction NONI + NOP + } + 7 => match p.y { + 0 => Some(build_log_unimplemented("ld i,hl")), + 2 => Some(build_log_unimplemented("ld hl,i")), + _ => Some(build_noni_nop()), // Invalid instruction NONI + NOP + }, + _ => Some(build_noni_nop()), // Invalid instruction NONI + NOP + }, + _ => panic!("Unreachable") + }; + +/* + match opcode.as_ref() { + None => println!("0x{:02x} 0x{:02x} {:15}: {:?}", 0xed, c, "Pending", p), + Some(o) => println!("0x{:02x} 0x{:02x} {:15}: {:?}", 0xed, c, o.name, p) + } +*/ + self.prefix_ed[c as usize] = opcode; + } + } + + fn load_has_displacement(&mut self) { + self.has_displacement[0x34] = true; + self.has_displacement[0x35] = true; + self.has_displacement[0x36] = true; + self.has_displacement[0x46] = true; + self.has_displacement[0x4e] = true; + self.has_displacement[0x56] = true; + self.has_displacement[0x5e] = true; + self.has_displacement[0x66] = true; + self.has_displacement[0x6e] = true; + self.has_displacement[0x70] = true; + self.has_displacement[0x71] = true; + self.has_displacement[0x72] = true; + self.has_displacement[0x73] = true; + self.has_displacement[0x74] = true; + self.has_displacement[0x75] = true; + self.has_displacement[0x77] = true; + self.has_displacement[0x7e] = true; + self.has_displacement[0x86] = true; + self.has_displacement[0x8e] = true; + self.has_displacement[0x96] = true; + self.has_displacement[0x9e] = true; + self.has_displacement[0xa6] = true; + self.has_displacement[0xae] = true; + self.has_displacement[0xb6] = true; + self.has_displacement[0xbe] = true; + } +} + +#[derive(Debug)] +struct DecodingHelper { + // See notation in http://www.z80.info/decoding.htm + x: usize, + y: usize, + z: usize, + p: usize, + q: usize +} + +impl DecodingHelper { + fn parts(code: u8) -> DecodingHelper { + DecodingHelper { + x: (code >> 6) as usize, + y: ((code >> 3) & 7) as usize, + z: (code & 7) as usize, + p: ((code >> 4) & 3) as usize, + q: ((code >> 3) & 1) as usize, + } + } +} + + +pub const RP: [Reg16; 4] = [Reg16::BC, Reg16::DE, Reg16::HL, Reg16::SP]; +pub const RP2: [Reg16; 4] = [Reg16::BC, Reg16::DE, Reg16::HL, Reg16::AF]; +pub const R: [Reg8; 8] = [Reg8::B, Reg8::C, Reg8::D, Reg8::E, Reg8::H, Reg8::L, Reg8::_HL, Reg8::A]; +pub const IM: [u8; 8] = [0, 0, 1, 2, 0, 0, 1, 2]; + +pub const CC: [(Flag, bool, &str); 8] = [ + (Flag::Z, false, "NZ"), + (Flag::Z, true, "Z"), + (Flag::C, false, "NC"), + (Flag::C, true, "C"), + (Flag::P, false, "PO"), + (Flag::P, true, "PE"), + (Flag::S, false, "P"), + (Flag::S, true, "M") +]; + +pub const ROT: [(ShiftDir, ShiftMode, &str); 8] = [ + (ShiftDir::Left, ShiftMode::RotateCarry, "RLC"), + (ShiftDir::Right, ShiftMode::RotateCarry, "RRC"), + (ShiftDir::Left, ShiftMode::Rotate, "RL" ), + (ShiftDir::Right, ShiftMode::Rotate, "RR" ), + (ShiftDir::Left, ShiftMode::Arithmetic, "SLA"), + (ShiftDir::Right, ShiftMode::Arithmetic, "SRA"), + (ShiftDir::Left, ShiftMode::Logical, "SLL"), + (ShiftDir::Right, ShiftMode::Logical, "SRL"), +]; + +//pub const ALU: [(fn(&mut State, u8, u8) -> u8, &'static str); 8] = [ +pub const ALU: [(Operator, &str); 8] = [ + (operator_add, "ADD"), + (operator_adc, "ADC"), + (operator_sub, "SUB"), + (operator_sbc, "SBC"), + (operator_and, "AND"), + (operator_xor, "XOR"), + (operator_or, "OR"), + (operator_cp, "CP") +]; + +pub const BLI_A: [(bool, bool, &str); 4] = [ + (true, false, "I"), + (false, false, "D"), + (true, true, "IR"), + (false, true, "DR") +]; + +pub fn build_log_unimplemented(name: &'static str) -> Opcode { + Opcode { + name: name.to_string(), + action: Box::new(move |_: &mut Environment| { + println!("Unimplemented opcode: {}", name); + }) + } +} + diff --git a/src/decoder_z80.rs b/src/decoder_z80.rs index 011b428..8471628 100644 --- a/src/decoder_z80.rs +++ b/src/decoder_z80.rs @@ -343,8 +343,8 @@ impl DecoderZ80 { 7 => match p.y { 0 => Some(build_ld_r_r(Reg8::I, Reg8::A, true)), // LD I, A 1 => Some(build_ld_r_r(Reg8::R, Reg8::A, true)), // LD R, A - 2 => Some(build_ld_r_r(Reg8::A, Reg8::I, true)), // LD A, I - 3 => Some(build_ld_r_r(Reg8::A, Reg8::R, true)), // LD A, R + 2 => Some(build_ld_a_r_or_i(Reg8::I)), // LD A, I + 3 => Some(build_ld_a_r_or_i(Reg8::R)), // LD A, R 4 => Some(build_rxd(ShiftDir::Right, "RRD")), // RRD 5 => Some(build_rxd(ShiftDir::Left, "RLD")), // RLD 6 => Some(build_nop()), // NOP @@ -475,4 +475,4 @@ pub const BLI_A: [(bool, bool, &str); 4] = [ (false, false, "D"), (true, true, "IR"), (false, true, "DR") -]; \ No newline at end of file +]; diff --git a/src/disassembler.rs b/src/disassembler.rs new file mode 100644 index 0000000..3b4a086 --- /dev/null +++ b/src/disassembler.rs @@ -0,0 +1,64 @@ +use crate::machine::Machine; +use crate::cpu::Cpu; +use crate::environment::Environment; +use crate::registers::*; + +#[derive(Clone, Debug)] +pub struct Disasm { + pub loc: u32, + pub asm: String, + pub bytes: Vec +} + +/** + * Disassemble a section of code. + * + * Tries to not mutate state, but needs a mutable cpu ref... + * iz80 disassembly is a bit awkward due to the way it increments the PC + */ +pub fn disassemble(machine: &mut dyn Machine, cpu: &mut Cpu, adl_override: Option, start: u32, end: u32) -> Vec { + let mut dis: Vec = vec![]; + let old_state = cpu.state.clone(); + + if let Some(adl) = adl_override { + cpu.state.reg.adl = adl; + } + cpu.state.reg.pc = start; + cpu.state.reg.mbase = (start >> 16) as u8; + + while cpu.state.pc() < end { + + let opcode_start = cpu.state.pc(); + let opcode_asm = cpu.disasm_instruction(machine); + + // horrible. but adl/non adl wraparound is a pain + let mut instruction_bytes = vec![]; + { + let opcode_end = cpu.state.pc(); + let mut env = Environment::new(&mut cpu.state, machine); + env.state.reg.pc = opcode_start; + while env.state.reg.pc != opcode_end { + instruction_bytes.push(env.advance_pc()); + } + } + + dis.push(Disasm { + loc: opcode_start, + asm: opcode_asm, + bytes: instruction_bytes + }); + + cpu.state.clear_sz_prefix(); + cpu.state.index = Reg16::HL; + + // handle pc wraparound in ADL=0 mode + if cpu.state.reg.pc < opcode_start { + cpu.state.reg.mbase += 1; + } + } + + // restore old cpu state + cpu.state = old_state; + + dis +} diff --git a/src/environment.rs b/src/environment.rs index 03831c5..3ca06a0 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -1,6 +1,6 @@ use super::machine::*; use super::registers::*; -use super::state::State; +use super::state::{ State, SizePrefix }; pub struct Environment<'a> { pub state: &'a mut State, @@ -15,21 +15,113 @@ impl <'a> Environment<'_> { } } + pub fn wrap_address24(&self, address: u32, increment: i32) -> u32 { + address.wrapping_add(increment as u32) + } + + // wrap the low 16-bits, leaving the top byte unchanged + pub fn wrap_address16(&self, address: u32, increment: i32) -> u32 { + (address & 0xff0000) + (address as u16).wrapping_add(increment as u16) as u32 + } + + pub fn wrap_address(&self, address: u32, increment: i32) -> u32 { + if self.state.is_op_long() { + self.wrap_address24(address, increment) + } else { + self.wrap_address16(address, increment) + } + } + + pub fn interrupt(&mut self, number: u32) -> () { + if self.state.reg.get_iff1() { + let vector_address = ((self.state.reg.get8(Reg8::I) as u32) << 8) + number; + let vector = self.peek16(vector_address) as u32; + + // So cycles used (in MADL + ADL example) is: + // 3 to push PC + // 1 to push MADL state + // 2 for interrupt vector + // = 6 + // Measured interrupt entry cost on EZ80F92 is 11 cycles, so... + self.sys.use_cycles(5); + + self.state.reg.set_interrupts(false); + if self.state.reg.madl { + let pc = self.state.pc(); + if self.state.reg.adl { + self.push(pc); + self.push_byte_spl(3); + self.state.set_pc(vector); + } else { + self.push_byte_spl((pc >> 8) as u8); + self.push_byte_spl(pc as u8); + self.push_byte_spl(2); + self.state.reg.adl = true; + self.state.set_pc(vector); + } + } else { + self.subroutine_call(vector); + } + } + } + + pub fn peek(&self, address: u32) -> u8 { + self.sys.peek(address) + } + + /// Sets the memory content to [value] in [address] + pub fn poke(&mut self, address: u32, value: u8) { + self.sys.poke(address, value); + } + + /// Returns the memory contents in [address] as word + pub fn peek16(&self, address: u32) -> u16 { + self.sys.peek(address) as u16 + + ((self.sys.peek(self.wrap_address(address, 1)) as u16) << 8) + } + + /// Sets the memory content to the word [value] in [address] + pub fn poke16(&mut self, address: u32, value: u16) { + self.sys.poke(address, value as u8 ); + self.sys.poke(self.wrap_address(address, 1), (value >> 8) as u8); + } + + pub fn peek24(&self, address: u32) -> u32 { + self.sys.peek(address) as u32 + + ((self.sys.peek(self.wrap_address(address, 1)) as u32) << 8) + + ((self.sys.peek(self.wrap_address(address, 2)) as u32) << 16) + } + + pub fn poke24(&mut self, address: u32, value: u32) { + self.sys.poke(address, value as u8 ); + self.sys.poke(self.wrap_address(address, 1), (value >> 8) as u8); + self.sys.poke(self.wrap_address(address, 2), (value >> 16) as u8); + } + pub fn peek_pc(&self) -> u8 { - let pc = self.state.reg.pc(); + let pc = self.state.pc(); self.sys.peek(pc) } pub fn advance_pc(&mut self) -> u8 { - let pc = self.state.reg.pc(); + let pc = self.state.pc(); let value = self.sys.peek(pc); - self.state.reg.set_pc(pc.wrapping_add(1)); + if self.state.reg.adl { + self.state.set_pc(self.wrap_address24(pc, 1)); + } else { + self.state.set_pc(self.wrap_address16(pc, 1)); + } value } pub fn peek16_pc(&self) -> u16 { - let pc = self.state.reg.pc(); - self.sys.peek16(pc) + let pc = self.state.pc(); + self.peek16(pc) + } + + pub fn peek24_pc(&self) -> u32 { + let pc = self.state.pc(); + self.peek24(pc) } pub fn advance_immediate16(&mut self) -> u16 { @@ -38,42 +130,155 @@ impl <'a> Environment<'_> { value } - pub fn push(&mut self, value: u16) { - let mut sp = self.state.reg.get16(Reg16::SP); + pub fn advance_immediate24(&mut self) -> u32 { + let mut value = self.advance_pc() as u32; + value += (self.advance_pc() as u32) << 8; + value += (self.advance_pc() as u32) << 16; + value + } + + pub fn advance_immediate16or24(&mut self) -> u32 { + if self.state.is_imm_long() { + self.advance_immediate24() + } else { + self.advance_immediate16() as u32 + } + } + + pub fn advance_immediate_16mbase_or_24(&mut self) -> u32 { + let imm = if self.state.is_imm_long() { + self.advance_immediate24() + } else { + self.advance_immediate16() as u32 + }; - let h = (value >> 8) as u8; - let l = value as u8; + if self.state.is_op_long() { + imm + } else { + (imm & 0xffff) + ((self.state.reg.mbase as u32) << 16) + } + } - sp = sp.wrapping_sub(1); - self.sys.poke(sp, h); + pub fn push_byte_sps(&mut self, value: u8) { + let sps = self.wrap_address16( self.state.reg.get16_mbase(Reg16::SP), -1); + self.sys.poke(sps, value); + self.state.reg.set16(Reg16::SP, sps as u16); + } + + pub fn pop_byte_sps(&mut self) -> u8 { + let sps = self.state.reg.get16_mbase(Reg16::SP); + let l = self.sys.peek(sps); + self.state.reg.set16(Reg16::SP, self.wrap_address16(sps, 1) as u16); + l + } - sp = sp.wrapping_sub(1); - self.sys.poke(sp, l); + pub fn push_byte_spl(&mut self, value: u8) { + let spl = self.wrap_address24( self.state.reg.get24(Reg16::SP), -1); + self.sys.poke(spl, value); + self.state.reg.set24(Reg16::SP, spl); + } - self.state.reg.set16(Reg16::SP, sp); + pub fn pop_byte_spl(&mut self) -> u8 { + let spl = self.state.reg.get24(Reg16::SP); + let l = self.sys.peek(spl); + self.state.reg.set24(Reg16::SP, self.wrap_address24(spl, 1)); + l } - pub fn pop(&mut self) -> u16 { - let mut sp = self.state.reg.get16(Reg16::SP); + pub fn push(&mut self, value: u32) { + let u = (value >> 16) as u8; + let h = (value >> 8) as u8; + let l = value as u8; + + if self.state.is_op_long() { + self.push_byte_spl(u); + self.push_byte_spl(h); + self.push_byte_spl(l); + } else { + self.push_byte_sps(h); + self.push_byte_sps(l); + } + } - let l = self.sys.peek(sp); - sp = sp.wrapping_add(1); + pub fn pop(&mut self) -> u32 { + let u; + let h; + let l; - let h = self.sys.peek(sp); - sp = sp.wrapping_add(1); + if self.state.is_op_long() { + l = self.pop_byte_spl(); + h = self.pop_byte_spl(); + u = self.pop_byte_spl(); + } else { + l = self.pop_byte_sps(); + h = self.pop_byte_sps(); + u = 0; + } - self.state.reg.set16(Reg16::SP, sp); - (l as u16) + ((h as u16) << 8) + (l as u32) + ((h as u32) << 8) + ((u as u32) << 16) } - pub fn subroutine_call(&mut self, address: u16) { - self.push(self.state.reg.pc()); - self.state.reg.set_pc(address); + pub fn subroutine_call(&mut self, address: u32) { + self.push(self.state.pc()); + self.state.set_pc(address); } pub fn subroutine_return(&mut self) { - let pc = self.pop(); - self.state.reg.set_pc(pc); + if self.state.reg.adl { + match self.state.sz_prefix { + SizePrefix::None => { + let pc = self.pop(); + self.state.set_pc(pc); + } + // according to spec only LIL is valid here, but LIS does work too + SizePrefix::LIL | SizePrefix::LIS => { + let adl_flag = self.pop_byte_spl(); + if adl_flag & 1 == 1 { + let address = self.pop(); + self.state.set_pc(address); + } else { + let mut address = self.pop_byte_spl() as u32; + address += (self.pop_byte_spl() as u32) << 8; + self.state.set_pc(address); + self.state.reg.adl = false; + } + } + prefix => { + eprintln!("invalid size prefix {:?} to RET at PC=${:x}", prefix, self.state.pc()); + let pc = self.pop(); + self.state.set_pc(pc); + } + } + } else { + match self.state.sz_prefix { + SizePrefix::None => { + let pc = self.pop(); + self.state.set_pc(pc); + } + // according to spec, only LIS is valid here + // but it seems LIL does work from z80 mode... + SizePrefix::LIL | SizePrefix::LIS => { + let adl_flag = self.pop_byte_spl(); + if adl_flag & 1 == 1 { + let mut address = (self.pop_byte_spl() as u32) << 16; + address += self.pop_byte_sps() as u32; + address += (self.pop_byte_sps() as u32) << 8; + self.state.reg.adl = true; + self.state.set_pc(address); + } else { + let mut address = self.pop_byte_sps() as u32; + address += (self.pop_byte_sps() as u32) << 8; + self.state.reg.adl = false; + self.state.set_pc(address); + } + } + prefix => { + eprintln!("invalid size prefix {:?} to RET at PC=${:x}", prefix, self.state.pc()); + let pc = self.pop(); + self.state.set_pc(pc); + } + } + } } pub fn set_index(&mut self, index: Reg16) { @@ -84,6 +289,10 @@ impl <'a> Environment<'_> { self.state.index = Reg16::HL; } + pub fn get_index(&self) -> Reg16 { + self.state.index + } + pub fn index_description(&self) -> String { if self.state.index == Reg16::HL { "HL".to_string() @@ -108,15 +317,23 @@ impl <'a> Environment<'_> { self.state.displacement = self.advance_pc() as i8; } - pub fn index_value(& self) -> u16 { - self.state.reg.get16(self.state.index) + pub fn index_value(& self) -> u32 { + if self.state.is_op_long() { + self.state.reg.get24(self.state.index) + } else { + self.state.reg.get16_mbase(self.state.index) + } } - pub fn index_address(&self) -> u16 { + pub fn index_address(&self) -> u32 { // Pseudo register (HL), (IX+d), (IY+d) - let address = self.state.reg.get16(self.state.index); + let address = if self.state.is_op_long() { + self.state.reg.get24(self.state.index) + } else { + self.state.reg.get16_mbase(self.state.index) + }; if self.is_alt_index() { - (address as i16).wrapping_add(self.state.displacement as i16) as u16 + (address as i32).wrapping_add(self.state.displacement as i32) as u32 } else { address } @@ -146,11 +363,29 @@ impl <'a> Environment<'_> { } } - pub fn reg16_ext(& self, rr: Reg16) -> u16 { - if rr == Reg16::HL { - self.state.reg.get16(self.state.index) + pub fn reg16mbase_or_24(&mut self, rr: Reg16) -> u32 { + if self.state.is_op_long() { + self.state.reg.get24(rr) + } else { + self.state.reg.get16_mbase(rr) + } + } + + pub fn reg16or24_ext(& self, rr: Reg16) -> u32 { + if self.state.is_op_long() { + if rr == Reg16::HL { + self.state.reg.get24(self.state.index) + } else if rr == Reg16::AF { + self.state.reg.get16(rr) as u32 + } else { + self.state.reg.get24(rr) + } } else { - self.state.reg.get16(rr) + if rr == Reg16::HL { + self.state.reg.get16(self.state.index) as u32 + } else { + self.state.reg.get16(rr) as u32 + } } } @@ -162,6 +397,14 @@ impl <'a> Environment<'_> { } } + pub fn set_reg16or24(&mut self, rr: Reg16, value: u32) { + if self.state.is_op_long() { + self.set_reg24(rr, value); + } else { + self.set_reg16(rr, value as u16); + } + } + pub fn set_reg16(&mut self, rr: Reg16, value: u16) { if rr == Reg16::HL { self.state.reg.set16(self.state.index, value); @@ -170,6 +413,22 @@ impl <'a> Environment<'_> { } } + pub fn set_reg16_preserve_17_to_24(&mut self, rr: Reg16, value: u16) { + if rr == Reg16::HL { + self.state.reg.set16_preserve_17_to_24(self.state.index, value); + } else { + self.state.reg.set16_preserve_17_to_24(rr, value); + } + } + + pub fn set_reg24(&mut self, rr: Reg16, value: u32) { + if rr == Reg16::HL { + self.state.reg.set24(self.state.index, value); + } else { + self.state.reg.set24(rr, value); + } + } + pub fn port_in(&mut self, address: u16) -> u8 { self.sys.port_in(address) } diff --git a/src/lib.rs b/src/lib.rs index 6d8a732..94c3a22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -#![warn(missing_docs)] //! Z80 Emulator library that passes all ZEXALL tests //! //! See bin/cpuville.rs or the [iz-cpm](https://github.com/ivanizag/iz-cpm) @@ -8,7 +7,7 @@ //! To run this example, execute: `cargo run --bin simplest` //! //! ``` -//!use iz80::*; +//!use ez80::*; //! //!fn main() { //! // Prepare the device @@ -21,11 +20,11 @@ //! let code = [0x3c, 0xc3, 0x00, 0x00]; // INC A, JP $0000 //! let size = code.len(); //! for i in 0..size { -//! machine.poke(0x0000 + i as u16, code[i]); +//! machine.poke(i as u32, code[i]); //! } //! //! // Run emulation -//! cpu.registers().set_pc(0x0000); +//! cpu.state.set_pc(0x0000); //! loop { //! cpu.execute_instruction(&mut machine); //! @@ -45,6 +44,7 @@ mod registers; mod state; +mod decoder_ez80; mod decoder_z80; mod decoder_8080; mod environment; @@ -57,7 +57,11 @@ mod opcode_jumps; mod opcode_ld; mod operators; +pub mod disassembler; +pub mod z80_mem_tools; + pub use cpu::Cpu; pub use machine::Machine; pub use machine::PlainMachine; -pub use registers::*; \ No newline at end of file +pub use registers::*; +pub use environment::Environment; diff --git a/src/machine.rs b/src/machine.rs index 2f902c0..2b82ddc 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -5,21 +5,39 @@ /// provided with PlainMachine pub trait Machine { /// Returns the memory contents in [address] - fn peek(&self, address: u16) -> u8; + fn peek(&self, address: u32) -> u8; /// Sets the memory content to [value] in [address] - fn poke(&mut self, address: u16, value: u8); + fn poke(&mut self, address: u32, value: u8); + + fn use_cycles(&self, cycles: i32); /// Returns the memory contents in [address] as word - fn peek16(&self, address: u16) -> u16 { + /// XXX wrapping is wrong in non-ADL ez80 + fn _peek16(&self, address: u32) -> u16 { self.peek(address) as u16 + ((self.peek(address.wrapping_add(1)) as u16) << 8) } /// Sets the memory content to the word [value] in [address] - fn poke16(&mut self, address: u16, value: u16) { + /// XXX wrapping is wrong in non-ADL ez80 + fn _poke16(&mut self, address: u32, value: u16) { + self.poke(address, value as u8 ); + self.poke(address.wrapping_add(1), (value >> 8) as u8); + } + + /// XXX wrapping is wrong in non-ADL ez80 + fn _peek24(&self, address: u32) -> u32 { + self.peek(address) as u32 + + ((self.peek(address.wrapping_add(1)) as u32) << 8) + + ((self.peek(address.wrapping_add(2)) as u32) << 16) + } + + /// XXX wrapping is wrong in non-ADL ez80 + fn _poke24(&mut self, address: u32, value: u32) { self.poke(address, value as u8 ); self.poke(address.wrapping_add(1), (value >> 8) as u8); + self.poke(address.wrapping_add(2), (value >> 16) as u8); } /// Port in, from the device to the CPU. Returns the port value @@ -35,18 +53,28 @@ pub trait Machine { /// A minimum implementation of Machine. It uses two arrays of 65536 bytes to back the peeks and /// pokes to memory and the ins and outs of ports. pub struct PlainMachine { - mem: [u8; 65536], - io: [u8; 65536] + mem: [u8; 4*65536], + io: [u8; 4*65536], + pub elapsed_cycles: std::cell::Cell } impl PlainMachine { /// Returns a new PlainMachine instance pub fn new() -> PlainMachine { PlainMachine { - mem: [0; 65536], - io: [0; 65536] + mem: [0; 4*65536], + io: [0; 4*65536], + elapsed_cycles: std::cell::Cell::new(0) } } + + pub fn get_elapsed_cycles(&self) -> i64 { + self.elapsed_cycles.get() + } + + pub fn set_elapsed_cycles(&self, cycles: i64) { + self.elapsed_cycles.set(cycles); + } } impl Default for PlainMachine { @@ -56,19 +84,27 @@ impl Default for PlainMachine { } impl Machine for PlainMachine { - fn peek(&self, address: u16) -> u8 { + fn peek(&self, address: u32) -> u8 { + self.use_cycles(1); self.mem[address as usize] } - fn poke(&mut self, address: u16, value: u8) { + fn poke(&mut self, address: u32, value: u8) { + self.use_cycles(1); self.mem[address as usize] = value; } fn port_in(&mut self, address: u16) -> u8 { + self.use_cycles(1); self.io[address as usize] } fn port_out(&mut self, address: u16, value: u8) { + self.use_cycles(1); self.io[address as usize] = value; } + + fn use_cycles(&self, _cycles: i32) { + self.elapsed_cycles.set(self.elapsed_cycles.get().wrapping_add(_cycles as i64)); + } } @@ -80,7 +116,7 @@ mod tests { #[test] fn set_get_byte() { let mut m = PlainMachine::new(); - const A:u16 = 0x2345; + const A:u32 = 0x2345; const V:u8 = 0xa0; m.poke(A, V); diff --git a/src/opcode.rs b/src/opcode.rs index 542ec88..2402a4f 100644 --- a/src/opcode.rs +++ b/src/opcode.rs @@ -1,3 +1,4 @@ +use super::state::SizePrefix; use super::environment::*; use super::registers::*; @@ -13,31 +14,70 @@ impl Opcode { (self.action)(env); } - pub fn disasm(&self, env: &Environment) -> String { - let name = if self.name.contains("__index") { + /// returns String, and u32 PC increment due to immediates + /// (the PC increment due to the opcode itself, (and due + /// to the state.index hack), have already been applied by + /// the decoder. + pub fn disasm(&self, env: &Environment) -> (String, u32) { + let mut name = if self.name.contains("__index") { self.name.replace("__index", &env.index_description()) } else { self.name.clone() }; - if self.name.contains("nn") { - // Immediate argument 16 bits - let nn = env.peek16_pc(); - let nn_str = format!("{:04x}h", nn); - name.replace("nn", &nn_str) - } else if self.name.contains('n') { + match env.state.sz_prefix { + SizePrefix::None => {} + _ => { + if let Some(after_opcode_pos) = name.find(' ') { + name.insert_str(after_opcode_pos, &env.state.sz_prefix.to_string()); + } else { + name += &env.state.sz_prefix.to_string(); + } + } + } + + // hack. when the env.state.index trick is in use to change HL + // to IX or IY, modify the opcode text. Note that for opcodes + // where HL can appear as the non-index operand (eg LD HL,(IX+1)), + // this case is not triggered, because env.state.index is not + // used (see prefix_dd in decoder_ez80). + match env.state.index { + Reg16::IX => { name = name.replace("HL", "IX"); } + Reg16::IY => { name = name.replace("HL", "IY"); } + _ => {} + } + + if name.contains("nn") { + if env.state.is_imm_long() { + // Immediate argument 24 bits + let nn = env.peek24_pc(); + let nn_str = format!("${:x}", nn); + (name.replace("nn", &nn_str), 3) + } else { + // Immediate argument 16 bits + let nn = env.peek16_pc(); + let nn_str = format!("${:x}", nn); + (name.replace("nn", &nn_str), 2) + } + } else if name.contains('n') { // Immediate argument 8 bits let n = env.peek_pc(); - let n_str = format!("{:02x}h", n); - name.replace('n', &n_str) - } else if self.name.contains('d') { + let n_str = format!("${:x}", n); + (name.replace('n', &n_str), 1) + } else if name.contains('d') { // Immediate argument 8 bits signed // In assembly it's shown with 2 added as if it were from the opcode pc. - let d = env.peek_pc() as i8 as i16 + 2; - let d_str = format!("{:+x}", d); - name.replace('d', &d_str) + let d = env.peek_pc() as i8 as i16; + let d_str = if d < 0 { format!("-${:x}", -d) } else { format!("+${:x}", d) }; + (name.replace('d', &d_str), 1) + } else if name.contains('l') { + // Jump offset as 8 bits signed. + // In asm show as absolute address + let addr = (env.state.pc() as i32 + 1 + env.peek_pc() as i8 as i32) as u32; + let l_str = format!("${:x}", addr); + (name.replace('l', &l_str), 1) } else { - name + (name, 0) } } } @@ -74,7 +114,11 @@ pub fn build_pop_rr(rr: Reg16) -> Opcode { name: format!("POP {:?}", rr), action: Box::new(move |env: &mut Environment| { let value = env.pop(); - env.set_reg16(rr, value); + if env.state.is_op_long() && rr != Reg16::AF { + env.set_reg24(rr, value); + } else { + env.set_reg16(rr, value as u16); + } }) } } @@ -83,7 +127,7 @@ pub fn build_push_rr(rr: Reg16) -> Opcode { Opcode { name: format!("PUSH {:?}", rr), action: Box::new(move |env: &mut Environment| { - let value = env.reg16_ext(rr); + let value = env.reg16or24_ext(rr); env.push(value); }) } @@ -107,3 +151,21 @@ pub fn build_im(im: u8) -> Opcode { }) } } + +pub fn build_stmix() -> Opcode { + Opcode { + name: "STMIX".to_string(), + action: Box::new(move |env: &mut Environment| { + env.state.reg.madl = true; + }) + } +} + +pub fn build_rsmix() -> Opcode { + Opcode { + name: "RSMIX".to_string(), + action: Box::new(move |env: &mut Environment| { + env.state.reg.madl = false; + }) + } +} diff --git a/src/opcode_alu.rs b/src/opcode_alu.rs index 751c156..37e38c6 100644 --- a/src/opcode_alu.rs +++ b/src/opcode_alu.rs @@ -3,6 +3,78 @@ use super::environment::*; use super::registers::*; use super::operators::*; +pub fn build_lea_rr_ind_offset(dest: Reg16, src: Reg16) -> Opcode { + Opcode { + name: format!("LEA {:?}, {:?}d", dest, src), + action: Box::new(move |env: &mut Environment| { + let imm = env.advance_pc() as i8 as i32 as u32; + if env.state.is_op_long() { + let value = env.state.reg.get24(src).wrapping_add(imm); + env.state.reg.set24(dest, value); + } else { + let value = env.state.reg.get16(src).wrapping_add(imm as u16); + env.state.reg.set16(dest, value); + } + }) + } +} + +pub fn build_pea(src: Reg16) -> Opcode { + Opcode { + name: format!("PEA {:?}d", src), + action: Box::new(move |env: &mut Environment| { + let imm = env.advance_pc() as i8 as i32 as u32; + if env.state.is_op_long() { + let value = env.state.reg.get24(src).wrapping_add(imm); + env.push(value); + } else { + let value = env.state.reg.get16(src).wrapping_add(imm as u16); + env.push(value as u32); + } + }) + } +} + +pub fn build_tst_a_r(reg: Reg8) -> Opcode { + Opcode { + name: format!("TST A, {}", reg), + action: Box::new(move |env: &mut Environment| { + let a = env.state.reg.a(); + let b = env.reg8_ext(reg); + operator_tst(env, a, b); + }) + } +} + +pub fn build_tst_a_n() -> Opcode { + Opcode { + name: format!("TST A, n"), + action: Box::new(move |env: &mut Environment| { + let a = env.state.reg.a(); + let b = env.advance_pc(); + operator_tst(env, a, b); + }) + } +} + +pub fn build_operator_a_idx_offset(idx: Reg16, (op, name): (Operator, &str)) -> Opcode { + Opcode { + name: format!("{} A, ({:?}d)", name, idx), + action: Box::new(move |env: &mut Environment| { + let offset = env.advance_pc() as i8 as i32 as u32; + let a = env.state.reg.a(); + let address = if env.state.is_op_long() { + env.state.reg.get24(idx).wrapping_add(offset) + } else { + env.state.reg.get16_mbase_offset(idx, offset as u16) + }; + let b = env.peek(address); + let v = op(env, a, b); + env.state.reg.set_a(v); + }) + } +} + pub fn build_operator_a_r(r: Reg8, (op, name): (Operator, &str)) -> Opcode { if r != Reg8::_HL && r != Reg8::H && r != Reg8::L { // Fast version @@ -50,8 +122,13 @@ pub fn build_cp_block((inc, repeat, postfix) : (bool, bool, &'static str)) -> Op let b = env.reg8_ext(Reg8::_HL); let c_bak = env.state.reg.get_flag(Flag::C); operator_cp(env, a, b); - let bc = env.state.reg.inc_dec16(Reg16::BC, false /*decrement*/); - env.state.reg.inc_dec16(Reg16::HL, inc); + let bc = if env.state.is_op_long() { + env.state.reg.inc_dec24(Reg16::HL, inc); + env.state.reg.inc_dec24(Reg16::BC, false /*decrement*/) + } else { + env.state.reg.inc_dec16(Reg16::HL, inc); + env.state.reg.inc_dec16(Reg16::BC, false /*decrement*/) + }; // TUZD-4.2 let mut n = a.wrapping_sub(b); @@ -66,9 +143,22 @@ pub fn build_cp_block((inc, repeat, postfix) : (bool, bool, &'static str)) -> Op if repeat && bc != 0 && a != b { // Back to redo the instruction - let pc = env.state.reg.pc().wrapping_sub(2); - env.state.reg.set_pc(pc); + let pc = env.wrap_address(env.state.pc(), -2); + env.state.set_pc(pc); } }) } } + +pub fn build_mlt_rr(reg: Reg16) -> Opcode { + Opcode { + name: format!("MLT {:?}", reg), + action: Box::new(move |env: &mut Environment| { + let r = env.state.reg.get16(reg); + let a = r & 0xff; + let b = (r >> 8) & 0xff; + env.state.reg.set16(reg, a * b); + env.sys.use_cycles(4); + }) + } +} diff --git a/src/opcode_arith.rs b/src/opcode_arith.rs index afcc96c..b8a0875 100644 --- a/src/opcode_arith.rs +++ b/src/opcode_arith.rs @@ -9,9 +9,15 @@ pub fn build_add_hl_rr(rr: Reg16) -> Opcode { name: format!("ADD HL, {:?}", rr), action: Box::new(move |env: &mut Environment| { let aa = env.index_value(); - let bb = env.reg16_ext(rr); - let vv = operator_add16(env, aa, bb); - env.set_reg16(Reg16::HL, vv); + let bb = env.reg16or24_ext(rr); + + if env.state.is_op_long() { + let vv = operator_add24(env, aa, bb); + env.set_reg24(Reg16::HL, vv); + } else { + let vv = operator_add16(env, aa as u16, bb as u16); + env.set_reg16(Reg16::HL, vv); + } }) } } @@ -21,9 +27,15 @@ pub fn build_adc_hl_rr(rr: Reg16) -> Opcode { name: format!("ADC HL, {:?}", rr), action: Box::new(move |env: &mut Environment| { let aa = env.index_value(); // This will always be HL. - let bb = env.reg16_ext(rr); - let vv = operator_adc16(env, aa, bb); - env.state.reg.set16(Reg16::HL, vv); + let bb = env.reg16or24_ext(rr); + + if env.state.is_op_long() { + let vv = operator_adc24(env, aa, bb); + env.state.reg.set24(Reg16::HL, vv); + } else { + let vv = operator_adc16(env, aa as u16, bb as u16); + env.state.reg.set16(Reg16::HL, vv); + } }) } } @@ -33,9 +45,14 @@ pub fn build_sbc_hl_rr(rr: Reg16) -> Opcode { name: format!("SBC HL, {:?}", rr), action: Box::new(move |env: &mut Environment| { let aa = env.index_value(); // This will always be HL. - let bb = env.reg16_ext(rr); - let vv = operator_sbc16(env, aa, bb); - env.state.reg.set16(Reg16::HL, vv); + let bb = env.reg16or24_ext(rr); + if env.state.is_op_long() { + let vv = operator_sbc24(env, aa, bb); + env.state.reg.set24(Reg16::HL, vv); + } else { + let vv = operator_sbc16(env, aa as u16, bb as u16); + env.state.reg.set16(Reg16::HL, vv); + } }) } } @@ -65,14 +82,18 @@ pub fn build_dec_r(r: Reg8) -> Opcode { } pub fn build_inc_dec_rr(rr: Reg16, inc: bool) -> Opcode { - let delta = if inc {1} else {-1_i16 as u16}; + let delta = if inc {1} else {-1_i32 as u32}; let mnemonic = if inc {"INC"} else {"DEC"}; Opcode { name: format!("{} {:?}", mnemonic, rr), action: Box::new(move |env: &mut Environment| { - let mut v = env.reg16_ext(rr); + let mut v = env.reg16or24_ext(rr); v = v.wrapping_add(delta); - env.set_reg16(rr, v); + if env.state.is_op_long() { + env.set_reg24(rr, v); + } else { + env.set_reg16(rr, v as u16); + } // Note: flags not affected on the 16 bit INC and DEC }) } diff --git a/src/opcode_bits.rs b/src/opcode_bits.rs index d035b5b..5e5fc78 100644 --- a/src/opcode_bits.rs +++ b/src/opcode_bits.rs @@ -18,7 +18,7 @@ pub enum ShiftDir { pub fn build_rot_r(r: Reg8, (dir, mode, name): (ShiftDir, ShiftMode, &str), fast: bool, indexed: bool) -> Opcode { let full_name = if indexed { - format!("LD {}, {} {}", r, name, Reg8::_HL) + format!("{} {}, {}", name, r, Reg8::_HL) } else { let separator = if fast {""} else {" "}; format!("{}{}{}", name, separator, r) diff --git a/src/opcode_io.rs b/src/opcode_io.rs index 5a3d523..b020385 100644 --- a/src/opcode_io.rs +++ b/src/opcode_io.rs @@ -47,6 +47,29 @@ pub fn build_out_n_a() -> Opcode { } } +pub fn build_out0_n_r(r: Reg8) -> Opcode { + Opcode { + name: format!("OUT0 (n), {}", r), + action: Box::new(move |env: &mut Environment| { + let address = env.advance_pc() as u16; + let data = env.state.reg.get8(r); + env.port_out(address, data); + }) + } +} + +pub fn build_in0_r_n(r: Reg8) -> Opcode { + Opcode { + name: format!("IN0 {}, (n)", r), + action: Box::new(move |env: &mut Environment| { + let address = env.advance_pc() as u16; + let data = env.port_in(address); + env.state.reg.update_arithmetic_flags(data as u16, data as u16, data as u16, true, false); + env.state.reg.set8(r, data); + }) + } +} + pub fn build_in_r_c(r: Reg8) -> Opcode { Opcode { name: format!("IN {}, (C)", r), @@ -100,7 +123,11 @@ pub fn build_in_block((inc, repeat, postfix) : (bool, bool, &'static str)) -> Op let value = env.port_in(address); // We won't have IX and IY cases to consider env.set_reg(Reg8::_HL, value); - env.state.reg.inc_dec16(Reg16::HL, inc); + if env.state.is_op_long() { + env.state.reg.inc_dec24(Reg16::HL, inc); + } else { + env.state.reg.inc_dec16(Reg16::HL, inc); + } // TUZD-4.3 let mut j = env.state.reg.get8(Reg8::C) as u16; @@ -110,8 +137,8 @@ pub fn build_in_block((inc, repeat, postfix) : (bool, bool, &'static str)) -> Op if repeat && b != 0 { // Back to redo the instruction - let pc = env.state.reg.pc().wrapping_sub(2); - env.state.reg.set_pc(pc); + let pc = env.wrap_address(env.state.pc(), -2); + env.state.set_pc(pc); } }) } @@ -129,7 +156,11 @@ pub fn build_out_block((inc, repeat, postfix) : (bool, bool, &'static str)) -> O // We won't have IX and IY cases to consider let value = env.reg8_ext(Reg8::_HL); env.port_out(address, value); - env.state.reg.inc_dec16(Reg16::HL, inc); + if env.state.is_op_long() { + env.state.reg.inc_dec24(Reg16::HL, inc); + } else { + env.state.reg.inc_dec16(Reg16::HL, inc); + } // TUZD-4.3 let k = value as u16 + env.state.reg.get8(Reg8::L) as u16; @@ -137,9 +168,56 @@ pub fn build_out_block((inc, repeat, postfix) : (bool, bool, &'static str)) -> O if repeat && b != 0 { // Back to redo the instruction - let pc = env.state.reg.pc().wrapping_sub(2); - env.state.reg.set_pc(pc); + let pc = env.wrap_address(env.state.pc(), -2); + env.state.set_pc(pc); + } + }) + } +} + +pub fn build_otirx_or_otdrx(inc: bool) -> Opcode { + Opcode { + name: format!("OT{}RX", if inc { 'I' } else { 'D' }), + action: Box::new(move |env: &mut Environment| { + let value = env.reg8_ext(Reg8::_HL); + let address = env.state.reg.get16(Reg16::DE); + env.sys.use_cycles(1); + + let bc = if env.state.is_op_long() { + env.state.reg.inc_dec24(Reg16::HL, inc); + env.state.reg.inc_dec24(Reg16::BC, false /*decrement*/) + } else { + env.state.reg.inc_dec16(Reg16::HL, inc); + env.state.reg.inc_dec16(Reg16::BC, false /*decrement*/) + }; + + if env.state.cached_instruction { + env.sys.use_cycles(-2); + } + + env.port_out(address, value); + + // TUZD-4.3 + env.state.reg.put_flag(Flag::Z, bc == 0); + env.state.reg.put_flag(Flag::N, if value & 0x80 == 0x80 { true } else { false }); + + if bc != 0 { + // Back to redo the instruction + let instruction_len = match env.state.sz_prefix { + crate::state::SizePrefix::None => 2, + _ => 3 + }; + let pc = env.wrap_address(env.state.pc(), -instruction_len); + env.state.set_pc(pc); + // and the size prefix is cached if present + if let crate::state::SizePrefix::None = env.state.sz_prefix { + } else { + env.sys.use_cycles(-1); + } + env.state.cached_instruction = true; + } else { + env.state.cached_instruction = false; } }) } -} \ No newline at end of file +} diff --git a/src/opcode_jumps.rs b/src/opcode_jumps.rs index c1d3130..d4307da 100644 --- a/src/opcode_jumps.rs +++ b/src/opcode_jumps.rs @@ -1,17 +1,19 @@ use super::opcode::*; use super::environment::*; use super::registers::*; +use super::state::SizePrefix; // Relative jumps pub fn build_djnz() -> Opcode { Opcode { - name: "DJNZ d".to_string(), + name: "DJNZ l".to_string(), action: Box::new(move |env: &mut Environment| { let offset = env.advance_pc(); let b = env.state.reg.get8(Reg8::B).wrapping_add(0xff /* -1 */); env.state.reg.set8(Reg8::B, b); if b != 0 { // Condition not met + env.sys.use_cycles(2); relative_jump(env, offset); } }) @@ -20,9 +22,10 @@ pub fn build_djnz() -> Opcode { pub fn build_jr_unconditional() -> Opcode { Opcode { - name: "JR d".to_string(), + name: "JR l".to_string(), action: Box::new(move |env: &mut Environment| { let offset = env.advance_pc(); + env.sys.use_cycles(1); relative_jump(env, offset); }) } @@ -30,10 +33,11 @@ pub fn build_jr_unconditional() -> Opcode { pub fn build_jr_eq((flag, value, name): (Flag, bool, &str)) -> Opcode { Opcode { - name: format!("JR {}, d", name), + name: format!("JR {}, l", name), action: Box::new(move |env: &mut Environment| { let offset = env.advance_pc(); if env.state.reg.get_flag(flag) == value { + env.sys.use_cycles(2); relative_jump(env, offset); } }) @@ -42,9 +46,30 @@ pub fn build_jr_eq((flag, value, name): (Flag, bool, &str)) -> Opcode { fn relative_jump(env: &mut Environment, offset: u8) { - let mut pc = env.state.reg.pc(); - pc = pc.wrapping_add(offset as i8 as i16 as u16); - env.state.reg.set_pc(pc); + let mut pc = env.state.pc(); + pc = env.wrap_address(pc, offset as i8 as i32); + env.state.set_pc(pc); +} + +fn handle_jump_adl_state(env: &mut Environment) { + if env.state.reg.adl { + match env.state.sz_prefix { + SizePrefix::SIS => { env.state.reg.adl = false }, + SizePrefix::LIS | SizePrefix::SIL => { + eprintln!("Invalid size prefix for ADL=1 with jump at PC=${:x}", env.state.pc()); + } + SizePrefix::LIL | + SizePrefix::None => {} + } + } else { + match env.state.sz_prefix { + SizePrefix::LIL => { env.state.reg.adl = true }, + SizePrefix::LIS | SizePrefix::SIL => { + eprintln!("Invalid size prefix for ADL=0 with jump at PC=${:x}", env.state.pc()); + }, + SizePrefix::SIS | SizePrefix::None => {} + } + } } // Absolute jumps @@ -52,8 +77,10 @@ pub fn build_jp_unconditional() -> Opcode { Opcode { name: "JP nn".to_string(), action: Box::new(move |env: &mut Environment| { - let address = env.advance_immediate16(); - env.state.reg.set_pc(address); + let address = env.advance_immediate_16mbase_or_24(); + handle_jump_adl_state(env); + env.sys.use_cycles(1); + env.state.set_pc(address); }) } } @@ -62,9 +89,10 @@ pub fn build_jp_eq((flag, value, name): (Flag, bool, &str)) -> Opcode { Opcode { name: format!("JP {}, nn", name), action: Box::new(move |env: &mut Environment| { - let address = env.advance_immediate16(); + let address = env.advance_immediate_16mbase_or_24(); if env.state.reg.get_flag(flag) == value { - env.state.reg.set_pc(address); + env.sys.use_cycles(1); + env.state.set_pc(address); } }) } @@ -72,22 +100,76 @@ pub fn build_jp_eq((flag, value, name): (Flag, bool, &str)) -> Opcode { pub fn build_jp_hl() -> Opcode { Opcode { - name: "JP HL".to_string(), // Note: it is usaully written as JP (HL) + name: "JP (HL)".to_string(), action: Box::new(move |env: &mut Environment| { // Note: no displacement added to the index let address = env.index_value(); - env.state.reg.set_pc(address); + env.sys.use_cycles(1); + env.state.set_pc(address); }) } } +fn handle_call_size_prefix(env: &mut Environment) { + let pc = env.state.pc(); + + if env.state.reg.adl { + match env.state.sz_prefix { + SizePrefix::None => { + env.push(pc); // 3 bytes onto SPL + }, + SizePrefix::SIS | // not valid according to docs, but works + SizePrefix::LIS => { + env.push_byte_sps((pc >> 8) as u8); + env.push_byte_sps(pc as u8); + env.push_byte_spl((pc >> 16) as u8); + env.push_byte_spl(3); + env.state.reg.adl = false; + } + SizePrefix::LIL => { + env.push(pc); // 3 bytes onto SPL + env.push_byte_spl(3); + } + prefix => { + env.push(pc); // 3 bytes onto SPL + eprintln!("invalid call size prefix for ADL=1: {}", prefix); + } + } + } else { + match env.state.sz_prefix { + SizePrefix::None => { + env.push_byte_sps((pc >> 8) as u8); + env.push_byte_sps(pc as u8); + }, + SizePrefix::LIL | // not valid according to docs, but works + SizePrefix::SIL => { + env.push_byte_spl((pc >> 8) as u8); + env.push_byte_spl(pc as u8); + env.push_byte_spl(2); + env.state.reg.adl = true; + } + SizePrefix::SIS => { + env.push_byte_sps((pc >> 8) as u8); + env.push_byte_sps(pc as u8); + env.push_byte_spl(2); + } + SizePrefix::LIS => { + env.push_byte_spl((pc >> 8) as u8); + env.push_byte_spl(pc as u8); + eprintln!("invalid call size prefix for ADL=0: LIS"); + } + } + } +} + // Calls to subroutine pub fn build_call() -> Opcode { Opcode { name: "CALL nn".to_string(), action: Box::new(move |env: &mut Environment| { - let address = env.advance_immediate16(); - env.subroutine_call(address); + let address = env.advance_immediate16or24(); + handle_call_size_prefix(env); + env.state.set_pc(address); }) } } @@ -96,20 +178,71 @@ pub fn build_call_eq((flag, value, name): (Flag, bool, &str)) -> Opcode { Opcode { name: format!("CALL {}, nn", name), action: Box::new(move |env: &mut Environment| { - let address = env.advance_immediate16(); + let address = env.advance_immediate_16mbase_or_24(); if env.state.reg.get_flag(flag) == value { - env.subroutine_call(address); + handle_call_size_prefix(env); + env.state.set_pc(address); } }) } } +fn handle_rst_size_prefix(env: &mut Environment, vec: u32) { + let pc = env.state.pc(); + + if env.state.reg.adl { + match env.state.sz_prefix { + SizePrefix::None => { + env.push(pc); + env.state.set_pc(vec); + }, + SizePrefix::SIL => { + env.push_byte_sps((pc >> 8) as u8); + env.push_byte_sps(pc as u8); + env.push_byte_spl((pc >> 16) as u8); + env.push_byte_spl(3); + env.state.reg.pc = vec; + env.state.reg.adl = false; + } + SizePrefix::LIS | // not valid according to spec, but works on ez80 + SizePrefix::LIL => { + env.push(pc); + env.push_byte_spl(3); + env.state.reg.pc = vec; + } + SizePrefix::SIS => { + eprintln!("invalid rst size prefix"); + } + } + } else { + match env.state.sz_prefix { + SizePrefix::None => { + env.push(pc); + env.state.set_pc(vec); + }, + SizePrefix::SIL | // <- SIL forbidden by spec, but works on ez80 + SizePrefix::SIS => { + env.push(pc); + env.push_byte_spl(2); + env.state.reg.pc = vec; + } + SizePrefix::LIL | // <- LIL is forbidden by the spec, but work on ez80 + SizePrefix::LIS => { + env.push_byte_spl((pc >> 8) as u8); + env.push_byte_spl(pc as u8); + env.push_byte_spl(2); + env.state.reg.adl = true; + env.state.reg.pc = vec; + } + } + } +} pub fn build_rst(d: u8) -> Opcode { Opcode { name: format!("RST {:02x}h", d), action: Box::new(move |env: &mut Environment| { - let address = d as u16; - env.subroutine_call(address); + let address = d as u32; + handle_rst_size_prefix(env, address); }) } } @@ -120,6 +253,7 @@ pub fn build_ret() -> Opcode { Opcode { name: "RET".to_string(), action: Box::new(move |env: &mut Environment| { + env.sys.use_cycles(2); env.subroutine_return(); }) } @@ -129,6 +263,7 @@ pub fn build_reti() -> Opcode { Opcode { name: "RETI".to_string(), action: Box::new(move |env: &mut Environment| { + env.sys.use_cycles(2); env.subroutine_return(); }) } @@ -138,6 +273,7 @@ pub fn build_retn() -> Opcode { Opcode { name: "RETN".to_string(), action: Box::new(move |env: &mut Environment| { + env.sys.use_cycles(2); env.subroutine_return(); env.state.reg.end_nmi(); }) @@ -149,7 +285,10 @@ pub fn build_ret_eq((flag, value, name): (Flag, bool, &str)) -> Opcode { name: format!("RET {}", name), action: Box::new(move |env: &mut Environment| { if env.state.reg.get_flag(flag) == value { + env.sys.use_cycles(2); env.subroutine_return(); + } else { + env.sys.use_cycles(1); } }) } diff --git a/src/opcode_ld.rs b/src/opcode_ld.rs index 8cb0be5..4728ab1 100644 --- a/src/opcode_ld.rs +++ b/src/opcode_ld.rs @@ -49,8 +49,28 @@ use super::registers::*; TODO: ix and iy based opcodes- */ +pub fn build_ld_a_r_or_i(src: Reg8) -> Opcode { + assert!(src == Reg8::R || src == Reg8::I); + Opcode { + name: format!("LD A, {}", src), + action: Box::new(move |env: &mut Environment| { + let value = env.state.reg.get8(src); + env.state.reg.set8(Reg8::A, value); + + // special case. does set some flags + env.state.reg.put_flag(Flag::N, false); + env.state.reg.put_flag(Flag::H, false); + env.state.reg.put_flag(Flag::Z, value == 0); + env.state.reg.put_flag(Flag::S, (value as i8) < 0); + env.state.reg.put_flag(Flag::P, env.state.reg.iff2); + }) + } +} + // 8 bit load pub fn build_ld_r_r(dst: Reg8, src: Reg8, _special: bool) -> Opcode { + assert_ne!(src, Reg8::R); + assert_ne!(src, Reg8::I); if src != Reg8::_HL && dst != Reg8::_HL && src != Reg8::H && dst != Reg8::H && src != Reg8::L && dst != Reg8::L { @@ -102,8 +122,8 @@ pub fn build_ld_a_prr(rr: Reg16) -> Opcode { Opcode { name: format!("LD A, ({:?})", rr), action: Box::new(move |env: &mut Environment| { - let address = env.state.reg.get16(rr); - let value = env.sys.peek(address); + let address = env.reg16mbase_or_24(rr); + let value = env.peek(address); env.state.reg.set_a(value); }) } @@ -113,8 +133,8 @@ pub fn build_ld_a_pnn() -> Opcode { Opcode { name: "LD A, (nn)".to_string(), action: Box::new(move |env: &mut Environment| { - let address = env.advance_immediate16(); - let value = env.sys.peek(address); + let address = env.advance_immediate_16mbase_or_24(); + let value = env.peek(address); env.state.reg.set_a(value); }) } @@ -126,8 +146,8 @@ pub fn build_ld_prr_a(rr: Reg16) -> Opcode { name: format!("LD ({:?}), A", rr), action: Box::new(move |env: &mut Environment| { let value = env.state.reg.a(); - let address = env.state.reg.get16(rr); - env.sys.poke(address, value); + let address = env.reg16mbase_or_24(rr); + env.poke(address, value); }) } @@ -138,8 +158,8 @@ pub fn build_ld_pnn_a() -> Opcode { name: "LD (nn), A".to_string(), action: Box::new(move |env: &mut Environment| { let value = env.state.reg.a(); - let address = env.advance_immediate16(); - env.sys.poke(address, value); + let address = env.advance_immediate_16mbase_or_24(); + env.poke(address, value); }) } @@ -151,8 +171,8 @@ pub fn build_ld_rr_nn(rr: Reg16) -> Opcode { Opcode { name: format!("LD {:?}, nn", rr), action: Box::new(move |env: &mut Environment| { - let value = env.advance_immediate16(); - env.set_reg16(rr, value); + let value: u32 = env.advance_immediate16or24(); + env.set_reg16or24(rr, value); }) } } @@ -161,8 +181,12 @@ pub fn build_ld_sp_hl() -> Opcode { Opcode { name: "LD SP, HL".to_string(), action: Box::new(move |env: &mut Environment| { - let value = env.reg16_ext(Reg16::HL); - env.set_reg16(Reg16::SP, value); + let value = env.reg16or24_ext(Reg16::HL); + if env.state.is_op_long() { + env.set_reg24(Reg16::SP, value); + } else { + env.set_reg16(Reg16::SP, value as u16); + } }) } } @@ -171,9 +195,13 @@ pub fn build_ld_pnn_rr(rr: Reg16, _fast: bool) -> Opcode { Opcode { name: format!("LD (nn), {:?}", rr), action: Box::new(move |env: &mut Environment| { - let address = env.advance_immediate16(); - let value = env.reg16_ext(rr); - env.sys.poke16(address, value); + let address = env.advance_immediate_16mbase_or_24(); + let value = env.reg16or24_ext(rr); + if env.state.is_op_long() { + env.poke24(address, value); + } else { + env.poke16(address, value as u16); + } }) } } @@ -182,9 +210,14 @@ pub fn build_ld_rr_pnn(rr: Reg16, _fast: bool) -> Opcode { Opcode { name: format!("LD {:?}, (nn)", rr), action: Box::new(move |env: &mut Environment| { - let address = env.advance_immediate16(); - let value = env.sys.peek16(address); - env.set_reg16(rr, value); + let address = env.advance_immediate_16mbase_or_24(); + if env.state.is_op_long() { + let value = env.peek24(address); + env.set_reg24(rr, value); + } else { + let value = env.peek16(address); + env.set_reg16(rr, value); + } }) } } @@ -193,7 +226,7 @@ pub fn build_ex_af() -> Opcode { Opcode { name: "EX AF, AF'".to_string(), action: Box::new(|env: &mut Environment| { - env.state.reg.swap(Reg16::AF); + env.state.reg.swap16(Reg16::AF); }) } } @@ -202,9 +235,9 @@ pub fn build_exx() -> Opcode { Opcode { name: "EXX".to_string(), action: Box::new(|env: &mut Environment| { - env.state.reg.swap(Reg16::BC); - env.state.reg.swap(Reg16::DE); - env.state.reg.swap(Reg16::HL); // NO IX, IY variant + env.state.reg.swap24(Reg16::BC); + env.state.reg.swap24(Reg16::DE); + env.state.reg.swap24(Reg16::HL); // NO IX, IY variant }) } } @@ -213,9 +246,15 @@ pub fn build_ex_de_hl() -> Opcode { Opcode { name: "EX DE, HL".to_string(), action: Box::new(move |env: &mut Environment| { - let temp = env.state.reg.get16(Reg16::HL); // No IX/IY variant - env.state.reg.set16(Reg16::HL, env.state.reg.get16(Reg16::DE)); - env.state.reg.set16(Reg16::DE, temp); + if env.state.is_op_long() { + let temp = env.state.reg.get24(Reg16::HL); // No IX/IY variant + env.state.reg.set24(Reg16::HL, env.state.reg.get24(Reg16::DE)); + env.state.reg.set24(Reg16::DE, temp); + } else { + let temp = env.state.reg.get16(Reg16::HL); // No IX/IY variant + env.state.reg.set16(Reg16::HL, env.state.reg.get16(Reg16::DE)); + env.state.reg.set16(Reg16::DE, temp); + } }) } } @@ -224,11 +263,15 @@ pub fn build_ex_psp_hl() -> Opcode { Opcode { name: "EX (SP), HL".to_string(), action: Box::new(move |env: &mut Environment| { - let address = env.state.reg.get16(Reg16::SP); - - let temp = env.reg16_ext(Reg16::HL); - env.set_reg16(Reg16::HL, env.sys.peek16(address)); - env.sys.poke16(address, temp); + let address = env.state.sp(); + let temp = env.reg16or24_ext(Reg16::HL); + if env.state.is_op_long() { + env.set_reg24(Reg16::HL, env.peek24(address)); + env.poke24(address, temp); + } else { + env.set_reg16_preserve_17_to_24(Reg16::HL, env.peek16(address)); + env.poke16(address, temp as u16); + } }) } } @@ -238,12 +281,19 @@ pub fn build_ld_block((inc, repeat, postfix) : (bool, bool, &'static str)) -> Op name: format!("LD{}", postfix), action: Box::new(move |env: &mut Environment| { let value = env.reg8_ext(Reg8::_HL); - let address = env.state.reg.get16(Reg16::DE); - env.sys.poke(address, value); + let address = env.reg16mbase_or_24(Reg16::DE); + env.poke(address, value); + env.sys.use_cycles(1); - env.state.reg.inc_dec16(Reg16::DE, inc); - env.state.reg.inc_dec16(Reg16::HL, inc); - let bc = env.state.reg.inc_dec16(Reg16::BC, false /*decrement*/); + let bc = if env.state.is_op_long() { + env.state.reg.inc_dec24(Reg16::DE, inc); + env.state.reg.inc_dec24(Reg16::HL, inc); + env.state.reg.inc_dec24(Reg16::BC, false /*decrement*/) + } else { + env.state.reg.inc_dec16(Reg16::DE, inc); + env.state.reg.inc_dec16(Reg16::HL, inc); + env.state.reg.inc_dec16(Reg16::BC, false /*decrement*/) + }; // TUZD-4.2 let n = value.wrapping_add(env.state.reg.a()); @@ -255,9 +305,109 @@ pub fn build_ld_block((inc, repeat, postfix) : (bool, bool, &'static str)) -> Op if repeat && bc != 0 { // Back to redo the instruction - let pc = env.state.reg.pc().wrapping_sub(2); - env.state.reg.set_pc(pc); + let instruction_len = match env.state.sz_prefix { + crate::state::SizePrefix::None => 2, + _ => 3 + }; + let pc = env.wrap_address(env.state.pc(), -instruction_len); + env.state.set_pc(pc); + // all but one repeat gets the 2-byte opcode cached + env.sys.use_cycles(-2); + // and the size prefix is cached if present + if let crate::state::SizePrefix::None = env.state.sz_prefix { + } else { + env.sys.use_cycles(-1); + } } }) } } + +pub fn build_ld_a_mb() -> Opcode { + Opcode { + name: "LD A, MB".to_string(), + action: Box::new(|env: &mut Environment| { + env.state.reg.set8(Reg8::A, env.state.reg.mbase); + }) + } +} + +pub fn build_ld_mb_a() -> Opcode { + Opcode { + name: "LD MB, A".to_string(), + action: Box::new(|env: &mut Environment| { + env.state.reg.mbase = env.state.reg.get8(Reg8::A); + }) + } +} + +pub fn build_ld_idx_disp_rr(index_reg: Reg16, src: Reg16) -> Opcode { + Opcode { + name: format!("LD ({:?}d), {:?}", index_reg, src), + action: Box::new(move |env: &mut Environment| { + let imm = env.advance_pc() as i8 as i32 as u32; + if env.state.is_op_long() { + let value = env.state.reg.get24(src); + let address = env.state.reg.get24(index_reg).wrapping_add(imm); + env.poke24(address, value); + } else { + let value = env.state.reg.get16(src); + // this is wrong XXX only wrap the 16-bit part + let address = env.state.reg.get16_mbase(index_reg).wrapping_add(imm); + env.poke16(address, value); + } + }) + } +} + +pub fn build_ld_rr_idx_disp(dest: Reg16, index_reg: Reg16) -> Opcode { + Opcode { + name: format!("LD {:?}, ({:?}d)", dest, index_reg), + action: Box::new(move |env: &mut Environment| { + let imm = env.advance_pc() as i8 as i32 as u32; + if env.state.is_op_long() { + let address = env.state.reg.get24(index_reg).wrapping_add(imm); + let value = env.peek24(address); + env.state.reg.set24(dest, value); + } else { + let address = env.state.reg.get16_mbase(index_reg).wrapping_add(imm); + let value = env.peek16(address); + env.state.reg.set16(dest, value); + } + }) + } +} + +pub fn build_ld_rr_ind_hl(dest: Reg16) -> Opcode { + Opcode { + name: format!("LD {:?}, (HL)", dest), + action: Box::new(move |env: &mut Environment| { + if env.state.is_op_long() { + let address = env.state.reg.get24(Reg16::HL); + let value = env.peek24(address); + env.state.reg.set24(dest, value); + } else { + let address = env.state.reg.get16_mbase(Reg16::HL); + let value = env.peek16(address); + env.state.reg.set16(dest, value); + } + }) + } +} + +pub fn build_ld_ind_hl_rr(src: Reg16) -> Opcode { + Opcode { + name: format!("LD (HL), {:?}", src), + action: Box::new(move |env: &mut Environment| { + if env.state.is_op_long() { + let address = env.state.reg.get24(Reg16::HL); + let value = env.state.reg.get24(src); + env.poke24(address, value); + } else { + let address = env.state.reg.get16_mbase(Reg16::HL); + let value = env.state.reg.get16(src); + env.poke16(address, value); + } + }) + } +} diff --git a/src/operators.rs b/src/operators.rs index b8a06e8..b64ca13 100644 --- a/src/operators.rs +++ b/src/operators.rs @@ -58,6 +58,39 @@ pub fn operator_sbc16(env: &mut Environment, aa: u16, bb: u16) -> u16 { vv } +pub fn operator_add24(env: &mut Environment, aaa: u32, bbb: u32) -> u32 { + let vvvv = aaa + bbb; + + env.state.reg.update_add24_flags(aaa, bbb, vvvv); + vvvv & 0xffffff +} + +pub fn operator_sbc24(env: &mut Environment, aaa: u32, bbb: u32) -> u32 { + let mut vvvv = aaa.wrapping_sub(bbb); + if env.state.reg.get_flag(Flag::C) { + vvvv = vvvv.wrapping_sub(1); + } + let vvv = vvvv & 0xffffff; + + // TUZD-8.6 + env.state.reg.update_arithmetic_flags_24(aaa, bbb, vvvv, true); + env.state.reg.put_flag(Flag::Z, vvv == 0); + vvv +} + +pub fn operator_adc24(env: &mut Environment, aaa: u32, bbb: u32) -> u32 { + let mut vvvv = aaa.wrapping_add(bbb); + if env.state.reg.get_flag(Flag::C) { + vvvv = vvvv.wrapping_add(1); + } + let vvv = vvvv & 0xffffff; + + // TUZD-8.6 + env.state.reg.update_arithmetic_flags_24(aaa, bbb, vvvv, false); + env.state.reg.put_flag(Flag::Z, vvv == 0); + vvv +} + pub fn operator_inc(env: &mut Environment, a: u8) -> u8 { let aa = a as u16; let vv = aa + 1; @@ -114,3 +147,8 @@ pub fn operator_cp(env: &mut Environment, a: u8, b: u8) -> u8 { env.state.reg.update_undocumented_flags(b); a // Do not update the accumulator } + +pub fn operator_tst(env: &mut Environment, a: u8, b: u8) { + let v = a & b; + env.state.reg.update_logic_flags(a, b, v, true); +} diff --git a/src/registers.rs b/src/registers.rs index 6198933..f894dd1 100644 --- a/src/registers.rs +++ b/src/registers.rs @@ -7,58 +7,87 @@ pub enum Reg8 { A = 0, /// 8 bit register F, can be accessed vif the flags methods F = 1, // Flags + /// 8 bit register BCU + BCU = 2, /// 8 bit register B - B = 2, + B = 3, /// 8 bit register C - C = 3, + C = 4, + /// 8 bit register DEU + DEU = 5, /// 8 bit register D - D = 4, + D = 6, /// 8 bit register E - E = 5, + E = 7, + /// 8 bit register HLU + HLU = 8, /// 8 bit register H, high byte of HL - H = 6, + H = 9, /// 8 bit register L, low byte of HL - L = 7, + L = 10, /// 8 bit register I - I = 8, + I = 11, /// 8 bit register R - R = 9, + R = 12, + /// 8 bit register IXU + IXU = 13, /// High byte of IX - IXH = 10, + IXH = 14, /// Low byte of IX - IXL = 11, + IXL = 15, + /// 8 bit register IYU + IYU = 16, /// High byte of IY - IYH = 12, + IYH = 17, /// Low byte of IY - IYL = 13, - /// High byte of SP - SPH = 14, - /// Low byte of SP - SPL = 15, + IYL = 18, + /// High byte of SPS + SPSH = 19, + /// Low byte of SPS + SPSL = 20, + /// Top byte of SPL + SPLU = 21, + SPLH = 22, + SPLL = 23, /// Pseudo register, has to be replaced by (HL) - _HL = 16 // Invalid + _HL = 24 // Invalid } -const REG_COUNT8: usize = 16; +const REG_COUNT8: usize = 24; -/// 16 bit registers, composed from 8 bit registers +/// Long registers -- either 16 or 24 bits, depending on CPU mode. +/// SP is actually 2 separate registers, SPS (16-bit) and SPL (24-bit), +/// Which you access depends whether you use reg16, reg24, set16 or set24 +/// XXX Reg16 is a poor name #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Reg16 { - /// 16 bit register AF - AF = Reg8::A as isize, - /// 16 bit register BC - BC = Reg8::B as isize, - /// 16 bit register DE - DE = Reg8::D as isize, + AF, + BC, + DE, + HL, + IX, + IY, + SP, +} + +/* +/// 24 bit registers, composed from 8 bit registers +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Reg24 { + /// 24 bit register BC + BC = Reg8::BCU as isize, + /// 24 bit register DE + DE = Reg8::DEU as isize, /// 16 bit register HL - HL = Reg8::H as isize, - /// 16 bit register IX - IX = Reg8::IXH as isize, - /// 16 bit register IY - IY = Reg8::IYH as isize, - /// 16 bit register SP - SP = Reg8::SPH as isize + HL = Reg8::HLU as isize, + /// 24 bit register IX + IX = Reg8::IXU as isize, + /// 24 bit register IY + IY = Reg8::IYU as isize, + /// 24 bit register SPL + SPL = Reg8::SPU as isize } +*/ /// Z80 flags #[derive(Copy, Clone, Debug)] @@ -92,15 +121,18 @@ impl fmt::Display for Reg8 { } /// Z80 internal register values -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Registers { data: [u8; REG_COUNT8], shadow: [u8; REG_COUNT8], - pc: u16, - iff1: bool, - iff2: bool, + pub pc: u32, + pub iff1: bool, + pub iff2: bool, im: u8, - mode8080: bool + mode8080: bool, + pub adl: bool, // ez80 24-bit flat addressing mode + pub madl: bool, // ez80 + pub mbase: u8, // provides the top 8-bits of a 24-bit address when ez80 is in z80 mode } impl Registers { @@ -113,11 +145,14 @@ impl Registers { iff1: false, iff2: false, im: 0, - mode8080: false + mode8080: false, + adl: false, + madl: false, + mbase: 0, }; reg.set16(Reg16::AF, 0xffff); - reg.set16(Reg16::SP, 0xffff); + reg.set16(Reg16::SP, 0x0000); reg } @@ -169,19 +204,75 @@ impl Registers { v } + #[inline] + pub fn get16_mbase(&self, rr: Reg16) -> u32 { + ((self.mbase as u32) << 16) + self.get16(rr) as u32 + } + + #[inline] + pub fn get16_mbase_offset(&self, rr: Reg16, offset: u16) -> u32 { + // applies a 16-bit offset to the low 16-bits, wrapping within 16-bits, + // then set high byte of 24bits to mbase + ((self.mbase as u32) << 16) + self.get16(rr).wrapping_add(offset) as u32 + } + /// Returns the value of a 16 bit register #[inline] pub fn get16(&self, rr: Reg16) -> u16 { - self.data[rr as usize +1] as u16 - + ((self.data[rr as usize] as u16) << 8) + let r8 = self.map_reg16_to_reg8(rr); + self.data[r8 as usize +1] as u16 + + ((self.data[r8 as usize] as u16) << 8) + } + + fn map_reg16_to_reg8(&self, rr: Reg16) -> Reg8 { + match rr { + Reg16::AF => Reg8::A, + Reg16::BC => Reg8::B, + Reg16::DE => Reg8::D, + Reg16::HL => Reg8::H, + Reg16::IX => Reg8::IXH, + Reg16::IY => Reg8::IYH, + Reg16::SP => Reg8::SPSH, + } + } + + fn map_reg24_to_reg8(&self, rr: Reg16) -> Reg8 { + match rr { + Reg16::AF => panic!(), + Reg16::BC => Reg8::BCU, + Reg16::DE => Reg8::DEU, + Reg16::HL => Reg8::HLU, + Reg16::IX => Reg8::IXU, + Reg16::IY => Reg8::IYU, + Reg16::SP => Reg8::SPLU, + } } /// Sets the value of a 16 bit register. Changes the /// value of the two underlying 8 bit registers. #[inline] pub fn set16(&mut self, rr: Reg16, value: u16) { - self.data[rr as usize +1] = value as u8; - self.data[rr as usize] = (value >> 8) as u8; + let r8 = self.map_reg16_to_reg8(rr); + self.data[r8 as usize +1] = value as u8; + self.data[r8 as usize] = (value >> 8) as u8; + if rr != Reg16::AF && rr != Reg16::SP { + self.data[r8 as usize -1] = 0; + } + //if self.mode8080 && rr == Reg16::AF { + if self.mode8080 && rr == Reg16::AF { + // Ensure non existent flags have proper values + self.set_flag(Flag::N); + self.clear_flag(Flag::_3); + self.clear_flag(Flag::_5); + } + } + + /// ug. some 16-bit register writes preserve the top bits 17-24 + /// for example: ex (sp), hl + pub fn set16_preserve_17_to_24(&mut self, rr: Reg16, value: u16) { + let r8 = self.map_reg16_to_reg8(rr); + self.data[r8 as usize +1] = value as u8; + self.data[r8 as usize] = (value >> 8) as u8; //if self.mode8080 && rr == Reg16::AF { if self.mode8080 && rr == Reg16::AF { // Ensure non existent flags have proper values @@ -191,7 +282,7 @@ impl Registers { } } - pub(crate) fn inc_dec16(&mut self, rr: Reg16, inc: bool) -> u16 { + pub(crate) fn inc_dec16(&mut self, rr: Reg16, inc: bool) -> u32 { let mut v = self.get16(rr); if inc { v = v.wrapping_add(1); @@ -199,15 +290,50 @@ impl Registers { v = v.wrapping_sub(1); } self.set16(rr, v); + v as u32 + } + + pub(crate) fn inc_dec24(&mut self, rr: Reg16, inc: bool) -> u32 { + let mut v = self.get24(rr); + if inc { + v = v.wrapping_add(1); + } else { + v = v.wrapping_sub(1); + } + self.set24(rr, v); v } - pub(crate) fn swap(&mut self, rr: Reg16) { - let ih = rr as usize; + /// Returns the value of a 16 bit register + #[inline] + pub fn get24(&self, rr: Reg16) -> u32 { + let r8 = self.map_reg24_to_reg8(rr); + self.data[r8 as usize +2] as u32 + + ((self.data[r8 as usize +1] as u32) << 8) + + ((self.data[r8 as usize] as u32) << 16) + } + + /// Sets the value of a 24 bit register. Changes the + /// value of the three underlying 8 bit registers. + #[inline] + pub fn set24(&mut self, rr: Reg16, value: u32) { + let r8 = self.map_reg24_to_reg8(rr); + self.data[r8 as usize +2] = value as u8; + self.data[r8 as usize +1] = (value >> 8) as u8; + self.data[r8 as usize] = (value >> 16) as u8; + } + + pub(crate) fn swap16(&mut self, rr: Reg16) { + let ih = self.map_reg16_to_reg8(rr) as usize; mem::swap(&mut self.data[ih], &mut self.shadow[ih]); + mem::swap(&mut self.data[ih + 1], &mut self.shadow[ih + 1]); + } - let il = rr as usize + 1; - mem::swap(&mut self.data[il], &mut self.shadow[il]); + pub(crate) fn swap24(&mut self, rr: Reg16) { + let iu = self.map_reg24_to_reg8(rr) as usize; + mem::swap(&mut self.data[iu], &mut self.shadow[iu]); + mem::swap(&mut self.data[iu + 1], &mut self.shadow[iu + 1]); + mem::swap(&mut self.data[iu + 2], &mut self.shadow[iu + 2]); } /// Returns the value of a flag @@ -273,6 +399,16 @@ impl Registers { } } + pub(crate) fn update_add24_flags(&mut self, a: u32, b: u32, v: u32) { + // Flags are affected by the high order byte. + // S, Z and P/V are not updated + let xor = ((a ^ b ^ v) >> 16) as u16; + self.update_undocumented_flags((v >> 16) as u8); + self.put_flag(Flag::C, (xor >> 8 & 1) != 0); + self.put_flag(Flag::H, (xor >> 4 & 1) != 0); + self.clear_flag(Flag::N); + } + pub(crate) fn update_add16_flags(&mut self, a: u32, b: u32, v: u32) { if self.mode8080 { self.put_flag(Flag::C, (v & 0x10000) != 0); @@ -288,6 +424,10 @@ impl Registers { } } + pub(crate) fn update_arithmetic_flags_24(&mut self, a: u32, b: u32, reference: u32, neg: bool) { + self.update_arithmetic_flags((a >> 16) as u16, (b >> 16) as u16, (reference >> 16) as u16, neg, true); + } + pub(crate) fn update_arithmetic_flags_16(&mut self, a: u32, b: u32, reference: u32, neg: bool) { // No ADC or SBC on the 8080 self.update_arithmetic_flags((a >> 8) as u16, (b >> 8) as u16, (reference >> 8) as u16, neg, true); @@ -356,16 +496,8 @@ impl Registers { } } - /// Returns the program counter - #[inline] - pub fn pc(&self) -> u16 { - self.pc - } - - /// Changes the program counter - #[inline] - pub fn set_pc(&mut self, value: u16) { - self.pc = value; + pub fn get_iff1(&self) -> bool { + self.iff1 } pub(crate) fn set_interrupts(&mut self, v: bool) { @@ -424,4 +556,4 @@ mod tests { r.put_flag(Flag::P, false); assert_eq!(false, r.get_flag(Flag::P)); } -} \ No newline at end of file +} diff --git a/src/state.rs b/src/state.rs index 6b9a878..8e333f1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,9 +1,21 @@ use super::registers::*; +/// ez80 opcode "suffixes". we call them prefixes here +/// because they appear before the opcode in machine code +#[derive(Clone,Copy,Debug)] +pub enum SizePrefix { + None, + LIL, + LIS, + SIL, + SIS +} + /// Internal state of the CPU /// /// Stores the state of the registers and additional hidden execution /// state of the CPU. +#[derive(Clone)] pub struct State { /// Values of the Z80 registers pub reg: Registers, @@ -16,6 +28,9 @@ pub struct State { // Alternate index management pub index: Reg16, // Using HL, IX or IY pub displacement: i8, // Used for (IX+d) and (iY+d) + pub sz_prefix: SizePrefix, + pub instructions_executed: u64, + pub cached_instruction: bool, } impl State { @@ -28,6 +43,68 @@ impl State { reset_pending: false, index: Reg16::HL, displacement: 0, + sz_prefix: SizePrefix::None, + instructions_executed: 0, + cached_instruction: false, + } + } + + pub fn clear_sz_prefix(&mut self) { + self.sz_prefix = SizePrefix::None; + } + + pub fn is_op_long(&self) -> bool { + match self.sz_prefix { + SizePrefix::None => self.reg.adl, + SizePrefix::LIL => true, + SizePrefix::LIS => true, + SizePrefix::SIL => false, + SizePrefix::SIS => false + } + } + + pub fn is_imm_long(&self) -> bool { + match self.sz_prefix { + SizePrefix::None => self.reg.adl, + SizePrefix::LIL => true, + SizePrefix::LIS => false, + SizePrefix::SIL => true, + SizePrefix::SIS => false + } + } + + pub fn sp(&self) -> u32 { + if self.is_op_long() { + self.reg.get24(Reg16::SP) + } else { + self.reg.get16_mbase(Reg16::SP) + } + } + + /// Returns the program counter + #[inline] + pub fn pc(&self) -> u32 { + if self.reg.adl { + self.reg.pc + } else { + ((self.reg.mbase as u32) << 16) + (self.reg.pc & 0xffff) as u32 } } + + pub fn set_pc(&mut self, value: u32) { + self.reg.pc = value & 0xffffff; + } +} + +impl std::fmt::Display for SizePrefix { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", match self { + &SizePrefix::LIL => ".LIL", + &SizePrefix::LIS => ".LIS", + &SizePrefix::SIL => ".SIL", + &SizePrefix::SIS => ".SIS", + &SizePrefix::None => "", + + }) + } } diff --git a/src/z80_mem_tools.rs b/src/z80_mem_tools.rs new file mode 100644 index 0000000..bbcbad2 --- /dev/null +++ b/src/z80_mem_tools.rs @@ -0,0 +1,38 @@ +// misc Machine tools +use crate::Machine; + +pub fn memset(machine: &mut M, address: u32, fill: u8, count: u32) { + for loc in address..(address + count) { + machine.poke(loc, fill); + } +} + +pub fn memcpy_to_z80(machine: &mut M, start: u32, data: &[u8]) { + let mut loc = start; + for byte in data { + machine.poke(loc, *byte); + loc += 1; + } +} + +pub fn get_cstring(machine: &M, address: u32) -> Vec { + let mut s: Vec = vec![]; + let mut ptr = address; + + loop { + match machine.peek(ptr) { + 0 => break, + b => s.push(b) + } + ptr += 1; + } + s +} + +pub fn checksum(machine: &M, start: u32, len: u32) -> u32 { + let mut checksum = 0u32; + for i in (start..(start+len)).step_by(3) { + checksum ^= machine._peek24(i as u32); + } + checksum +} diff --git a/tests/cputest.rs b/tests/cputest.rs index 4305f62..34600f1 100644 --- a/tests/cputest.rs +++ b/tests/cputest.rs @@ -1,4 +1,4 @@ -use iz80::*; +use ez80::*; // Diagnostics II, version 1.2, CPU test by Supersoft Associates @@ -21,7 +21,7 @@ fn cpu_test(mut cpu: Cpu) { let code = CODE; let size = code.len(); for i in 0..size { - machine.poke(0x100 + i as u16, code[i]); + machine.poke(0x100 + i as u32, code[i]); } /* @@ -33,10 +33,10 @@ fn cpu_test(mut cpu: Cpu) { //let code = [0xD3, 0x00, 0xC9]; let code = [0xC9]; for i in 0..code.len() { - machine.poke(5 + i as u16, code[i]); + machine.poke(5 + i as u32, code[i]); } - cpu.registers().set_pc(0x100); + cpu.state.set_pc(0x100); let trace = false; cpu.set_trace(trace); let mut msg = String::new(); @@ -44,18 +44,18 @@ fn cpu_test(mut cpu: Cpu) { cpu.execute_instruction(&mut machine); // Avoid tracing the long loop - if cpu.registers().pc() == 0x31b3 { + if cpu.state.pc() == 0x31b3 { cpu.set_trace(false); - } else if cpu.registers().pc() == 0x31b5 { + } else if cpu.state.pc() == 0x31b5 { cpu.set_trace(trace); } - if cpu.registers().pc() == 0x0000 { + if cpu.state.pc() == 0x0000 { println!(""); break; } - if cpu.registers().pc() == 0x0005 { + if cpu.state.pc() == 0x0005 { match cpu.registers().get8(Reg8::C) { 2 => { // C_WRITE @@ -69,4 +69,4 @@ fn cpu_test(mut cpu: Cpu) { } assert_eq!(true, msg.contains("CPU TESTS OK")); -} \ No newline at end of file +} diff --git a/tests/disasm_ez80.rs b/tests/disasm_ez80.rs new file mode 100644 index 0000000..ec9d26f --- /dev/null +++ b/tests/disasm_ez80.rs @@ -0,0 +1,38 @@ +use ez80::*; + +fn test_disasm_z80(code: &[u8], expected: &str) { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + + for i in 0..code.len() { + sys.poke(i as u32, code[i]); + } + + let disasm = cpu.disasm_instruction(&mut sys); + assert_eq!(expected, disasm); +} + +#[test] +fn test_disasm_ld_hl_ix_n() { + test_disasm_z80(&[0xdd, 0x27, 0x18], "LD HL, (IX+$18)"); +} + +#[test] +fn test_disasm_ld_ix_n_hl() { + test_disasm_z80(&[0xdd, 0x2f, 0x18], "LD (IX+$18), HL"); +} + +#[test] +fn test_disasm_push_ix() { + test_disasm_z80(&[0xdd, 0xe5], "PUSH IX"); +} + +#[test] +fn test_disasm_suffix() { + test_disasm_z80(&[0x5b, 0xdd, 0xe5], "PUSH.LIL IX"); +} + +#[test] +fn test_disasm_push_hl() { + test_disasm_z80(&[0xe5], "PUSH HL"); +} diff --git a/tests/disasm_z80.rs b/tests/disasm_z80.rs index 195e9af..8985a64 100644 --- a/tests/disasm_z80.rs +++ b/tests/disasm_z80.rs @@ -1,11 +1,11 @@ -use iz80::*; +use ez80::*; fn test_disasm_z80(code: &[u8], expected: &str) { let mut sys = PlainMachine::new(); let mut cpu = Cpu::new(); for i in 0..code.len() { - sys.poke(i as u16, code[i]); + sys.poke(i as u32, code[i]); } let disasm = cpu.disasm_instruction(&mut sys); @@ -19,10 +19,10 @@ fn test_disasm_nop() { #[test] fn test_disasm_ld_hl_n() { - test_disasm_z80(&[0x36, 0x33], "LD (HL), 33h"); + test_disasm_z80(&[0x36, 0x33], "LD (HL), $33"); } #[test] fn test_disasm_ld_ix_d_n() { - test_disasm_z80(&[0xdd, 0x36, 22, 0x33], "LD (IX+22), 33h"); + test_disasm_z80(&[0xdd, 0x36, 22, 0x33], "LD (IX+22), $33"); } diff --git a/tests/ex8080.rs b/tests/ex8080.rs index d59d68d..e9b0433 100644 --- a/tests/ex8080.rs +++ b/tests/ex8080.rs @@ -1,4 +1,4 @@ -use iz80::*; +use ez80::*; /* 8080/8085 CPU Exerciser by Ian Bartholomew and Frank Cringles @@ -16,7 +16,7 @@ fn test_ex8080() { let code = CODE; let size = code.len(); for i in 0..size { - machine.poke(0x100 + i as u16, code[i]); + machine.poke(0x100 + i as u32, code[i]); } /* @@ -28,21 +28,21 @@ fn test_ex8080() { //let code = [0xD3, 0x00, 0xC9]; let code = [0xC9]; for i in 0..code.len() { - machine.poke(5 + i as u16, code[i]); + machine.poke(5 + i as u32, code[i]); } // Patch to run a single test let run_single_test = false; let single_test = 3; if run_single_test { - let mut test_start = machine.peek16(0x0120); + let mut test_start = machine._peek16(0x0120); test_start += single_test*2; - machine.poke16(0x0120, test_start); - machine.poke16(test_start + 2 , 0); + machine._poke16(0x0120, test_start); + machine._poke16(test_start as u32 + 2 , 0); } - cpu.registers().set_pc(0x100); + cpu.state.set_pc(0x100); let trace = false; cpu.set_trace(trace); let mut tests_passed = 0; @@ -51,7 +51,7 @@ fn test_ex8080() { if trace && false { // Test state - let addr = 0x1d80 as u16; + let addr = 0x1d80 as u32; print!("Zex state 0x{:04x}: ", addr); for i in 0..0x10 { print!("{:02x} ", machine.peek(addr + i)); @@ -59,12 +59,12 @@ fn test_ex8080() { println!(""); } - if cpu.registers().pc() == 0x0000 { + if cpu.state.pc() == 0x0000 { println!(""); break; } - if cpu.registers().pc() == 0x0005 { + if cpu.state.pc() == 0x0005 { match cpu.registers().get8(Reg8::C) { 2 => { // C_WRITE @@ -75,7 +75,7 @@ fn test_ex8080() { let mut address = cpu.registers().get16(Reg16::DE); let mut msg = String::new(); loop { - let ch = machine.peek(address) as char; + let ch = machine.peek(address as u32) as char; address += 1; if ch == '$'{ @@ -98,4 +98,4 @@ fn test_ex8080() { } else { assert_eq!(25, tests_passed); } -} \ No newline at end of file +} diff --git a/tests/ez80test.rs b/tests/ez80test.rs new file mode 100644 index 0000000..84c5d50 --- /dev/null +++ b/tests/ez80test.rs @@ -0,0 +1,543 @@ +use ez80::*; + +#[test] +fn test_ez80_z80() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + + sys.poke(0x0000, 0x01); // LD BC, $3456 ; db $12 + sys.poke(0x0001, 0x56); + sys.poke(0x0002, 0x34); + sys.poke(0x0003, 0x12); + cpu.registers().set16(Reg16::BC, 0x0000); + + cpu.execute_instruction(&mut sys); + assert_eq!(0x3456, cpu.registers().get16(Reg16::BC)); + assert_eq!(0x3, cpu.state.pc()); + + //assert_eq!(0x123456, cpu.registers().get24(Reg24::BC)); +} + +#[test] +fn test_ez80_adl() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + cpu.set_adl(true); + + sys.poke(0x0000, 0x01); // LD BC, $123456 + sys.poke(0x0001, 0x56); + sys.poke(0x0002, 0x34); + sys.poke(0x0003, 0x12); + cpu.registers().set24(Reg16::BC, 0x0000); + + cpu.execute_instruction(&mut sys); + assert_eq!(0x123456, cpu.registers().get24(Reg16::BC)); + assert_eq!(0x4, cpu.state.pc()); +} + +#[test] +fn test_ez80_mem_wrap() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + + cpu.registers().set16(Reg16::SP, 0xFFFF); + sys.poke(0x00000, 0xc1); // POP BC + sys.poke(0x0FFFF, 0xfe); // POP BC + sys.poke(0x10000, 0xca); // POP BC + + cpu.execute_instruction(&mut sys); + assert_eq!(0x0001, cpu.registers().get16(Reg16::SP)); + assert_eq!(0x00c1fe, cpu.registers().get24(Reg16::BC)); + + cpu.set_adl(true); + cpu.state.set_pc(0); + cpu.registers().set24(Reg16::BC, 0); + cpu.registers().set24(Reg16::SP, 0xFFFF); + + cpu.execute_instruction(&mut sys); + assert_eq!(0x10002, cpu.registers().get24(Reg16::SP)); + assert_eq!(0xcafe, cpu.registers().get24(Reg16::BC)); +} + +#[test] +fn test_ez80_pc_wrap() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + + cpu.registers().set8(Reg8::A, 0); + cpu.state.reg.mbase = 1; + cpu.state.set_pc(0xffff); + sys.poke(0x1FFFF, 0x3c); // INC A + sys.poke(0x20000, 0x3c); // INC A + // execute an inc (at 0xffff) and a nop (at 0x0) + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + + assert_eq!(0x10001, cpu.state.pc()); + assert_eq!(0x01, cpu.registers().get8(Reg8::A)); + + cpu.set_adl(true); + + cpu.registers().set8(Reg8::A, 0); + cpu.state.set_pc(0x1ffff); + sys.poke(0x1FFFF, 0x3c); // INC A + sys.poke(0x20000, 0x3c); // INC A + // execute an inc (at 0xffff) and a nop (at 0x0) + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + + assert_eq!(0x20001, cpu.state.pc()); + assert_eq!(0x02, cpu.registers().get8(Reg8::A)); +} + +#[test] +fn test_size_suffixes() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + + for adl in [false, true] { + cpu.set_adl(adl); + + cpu.state.set_pc(0); + cpu.registers().set24(Reg16::DE, 0); + cpu.registers().set24(Reg16::HL, 0); + cpu.registers().set24(Reg16::IX, 0); + cpu.registers().set24(Reg16::IY, 0); + + sys.poke(0x0000, 0x5b); // .LIL + sys.poke(0x0001, 0x21); // ld.lil hl, $123456 + sys.poke(0x0002, 0x56); + sys.poke(0x0003, 0x34); + sys.poke(0x0004, 0x12); + sys.poke(0x0005, 0x40); // .SIS + sys.poke(0x0006, 0x11); // ld.sis de, $789a + sys.poke(0x0007, 0x9a); + sys.poke(0x0008, 0x78); + sys.poke(0x0009, 0x49); // .LIS + sys.poke(0x000a, 0xdd); // ld.lis ix, $1234 + sys.poke(0x000b, 0x21); + sys.poke(0x000c, 0x34); + sys.poke(0x000d, 0x12); + sys.poke(0x000e, 0x52); // SIL + sys.poke(0x000f, 0xfd); // ld.sil iy, $789abc + sys.poke(0x0010, 0x21); + sys.poke(0x0011, 0xbc); + sys.poke(0x0012, 0x9a); + sys.poke(0x0013, 0x78); + sys.poke(0x0014, 0xff); + + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + + assert_eq!(0x0014, cpu.state.pc()); + assert_eq!(0x123456, cpu.registers().get24(Reg16::HL)); + // note we are assuming the top byte of the register, when + // using .s prefix, is zero. the actual spec says "undefined" + assert_eq!(0x789a, cpu.registers().get24(Reg16::DE)); + assert_eq!(0x1234, cpu.registers().get24(Reg16::IX)); + assert_eq!(0x9abc, cpu.registers().get24(Reg16::IY)); + } +} + +#[test] +fn test_madl() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + + sys.poke(0x0000, 0xed); // STMIX + sys.poke(0x0001, 0x7d); + sys.poke(0x0002, 0xed); // RSMIX + sys.poke(0x0003, 0x7e); + + assert_eq!(false, cpu.state.reg.madl); + cpu.execute_instruction(&mut sys); + assert_eq!(true, cpu.state.reg.madl); + cpu.execute_instruction(&mut sys); + assert_eq!(false, cpu.state.reg.madl); +} + +#[test] +fn test_ld_mb() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + cpu.set_adl(true); + + cpu.state.reg.mbase = 2; + cpu.state.reg.set8(Reg8::A, 0); + + sys.poke(0x0000, 0xed); // LD A,MB + sys.poke(0x0001, 0x6e); + sys.poke(0x0002, 0x3c); // INC A + sys.poke(0x0003, 0xed); // LD MB,A + sys.poke(0x0004, 0x6d); + + assert_eq!(2, cpu.state.reg.mbase); + cpu.execute_instruction(&mut sys); + assert_eq!(2, cpu.registers().get8(Reg8::A)); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + assert_eq!(3, cpu.state.reg.mbase); +} + +#[test] +fn test_tst_a_hl() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + cpu.set_adl(true); + + sys.poke(0x0000, 0xed); // TST A,(HL) + sys.poke(0x0001, 0x34); + + cpu.state.reg.set8(Reg8::A, 0); + cpu.state.reg.set24(Reg16::HL, 0); + cpu.state.set_pc(0); + + cpu.execute_instruction(&mut sys); + + assert_eq!(2, cpu.state.pc()); + assert_eq!(0, cpu.registers().a()); + assert_eq!(false, cpu.registers().get_flag(Flag::C)); + assert_eq!(false, cpu.registers().get_flag(Flag::N)); + assert_eq!(false, cpu.registers().get_flag(Flag::S)); + + cpu.state.reg.set8(Reg8::A, 0xff); + cpu.state.reg.set24(Reg16::HL, 0); + cpu.state.set_pc(0); + + cpu.execute_instruction(&mut sys); + + assert_eq!(2, cpu.state.pc()); + assert_eq!(0xff, cpu.registers().a()); + assert_eq!(false, cpu.registers().get_flag(Flag::C)); + assert_eq!(false, cpu.registers().get_flag(Flag::N)); + assert_eq!(true, cpu.registers().get_flag(Flag::S)); +} + +#[test] +fn test_tst_a_n() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + cpu.set_adl(true); + + sys.poke(0x0000, 0xed); // TST A, 0xed + sys.poke(0x0001, 0x64); + sys.poke(0x0002, 0xed); + + cpu.state.reg.set8(Reg8::A, 0); + cpu.state.set_pc(0); + + cpu.execute_instruction(&mut sys); + + assert_eq!(3, cpu.state.pc()); + assert_eq!(0, cpu.registers().a()); + assert_eq!(false, cpu.registers().get_flag(Flag::C)); + assert_eq!(false, cpu.registers().get_flag(Flag::N)); + assert_eq!(false, cpu.registers().get_flag(Flag::S)); + + cpu.state.reg.set8(Reg8::A, 0xff); + cpu.state.set_pc(0); + + cpu.execute_instruction(&mut sys); + + assert_eq!(3, cpu.state.pc()); + assert_eq!(0xff, cpu.registers().a()); + assert_eq!(false, cpu.registers().get_flag(Flag::C)); + assert_eq!(false, cpu.registers().get_flag(Flag::N)); + assert_eq!(true, cpu.registers().get_flag(Flag::S)); +} + +#[test] +fn test_tst_a_r() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + cpu.set_adl(true); + + sys.poke(0x0000, 0xed); // TST A, B + sys.poke(0x0001, 0x04); + + cpu.state.reg.set8(Reg8::A, 0); + cpu.state.reg.set8(Reg8::B, 0xed); + cpu.state.set_pc(0); + + cpu.execute_instruction(&mut sys); + + assert_eq!(2, cpu.state.pc()); + assert_eq!(0, cpu.registers().a()); + assert_eq!(false, cpu.registers().get_flag(Flag::C)); + assert_eq!(false, cpu.registers().get_flag(Flag::N)); + assert_eq!(false, cpu.registers().get_flag(Flag::S)); + + cpu.state.reg.set8(Reg8::A, 0xff); + cpu.state.set_pc(0); + + cpu.execute_instruction(&mut sys); + + assert_eq!(2, cpu.state.pc()); + assert_eq!(0xff, cpu.registers().a()); + assert_eq!(false, cpu.registers().get_flag(Flag::C)); + assert_eq!(false, cpu.registers().get_flag(Flag::N)); + assert_eq!(true, cpu.registers().get_flag(Flag::S)); +} + +#[test] +fn test_pea() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + cpu.set_adl(true); + + sys.poke(0x0000, 0xed); // PEA IX+$12 + sys.poke(0x0001, 0x65); + sys.poke(0x0002, 0x12); + + cpu.state.reg.set24(Reg16::IX, 0xabcdef); + cpu.state.reg.set24(Reg16::SP, 0x100); + + cpu.execute_instruction(&mut sys); + + assert_eq!(3, cpu.state.pc()); + assert_eq!(0xfd, cpu.state.sp()); + assert_eq!(0x01, sys.peek(0xfd)); + assert_eq!(0xce, sys.peek(0xfe)); + assert_eq!(0xab, sys.peek(0xff)); +} + +#[test] +fn test_alu_ixh_ihl() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + cpu.set_adl(true); + + sys.poke(0x0000, 0xaf); // xor a + sys.poke(0x0001, 0xdd); // ld ix, $cafeba + sys.poke(0x0002, 0x21); + sys.poke(0x0003, 0xba); + sys.poke(0x0004, 0xfe); + sys.poke(0x0005, 0xca); + sys.poke(0x0006, 0xdd); // add a,ixh + sys.poke(0x0007, 0x84); + sys.poke(0x0008, 0xdd); // add a,ihl + sys.poke(0x0009, 0x85); + + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + + assert_eq!(0xcafeba, cpu.state.reg.get24(Reg16::IX)); + assert_eq!(0x0, cpu.state.reg.a()); + + cpu.execute_instruction(&mut sys); + + assert_eq!(0xfe, cpu.state.reg.a()); + + cpu.execute_instruction(&mut sys); + + assert_eq!(0xb8, cpu.state.reg.a()); +} + +#[test] +fn test_alu_ld_ixh_ixl() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + cpu.set_adl(true); + cpu.state.reg.set24(Reg16::IX, 0xcafeba); + + sys.poke(0x0000, 0xdd); // ld ixh,$de + sys.poke(0x0001, 0x26); + sys.poke(0x0002, 0xde); + sys.poke(0x0003, 0xdd); // ld ixl,$89 + sys.poke(0x0004, 0x2e); + sys.poke(0x0005, 0x89); + sys.poke(0x0006, 0xdd); // dec ixh + sys.poke(0x0007, 0x25); + sys.poke(0x0008, 0xdd); // inc ixh + sys.poke(0x0009, 0x24); + + cpu.execute_instruction(&mut sys); + assert_eq!(0xcadeba, cpu.state.reg.get24(Reg16::IX)); + cpu.execute_instruction(&mut sys); + assert_eq!(0xcade89, cpu.state.reg.get24(Reg16::IX)); + cpu.execute_instruction(&mut sys); + assert_eq!(0xcadd89, cpu.state.reg.get24(Reg16::IX)); + cpu.execute_instruction(&mut sys); + assert_eq!(0xcade89, cpu.state.reg.get24(Reg16::IX)); +} + +#[test] +fn test_24bit_alu_flags() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + + // 16-bit cp hl,de + sys.poke(0x0000, 0x0); // nop + sys.poke(0x0001, 0xed); // sbc hl,de + sys.poke(0x0002, 0x52); + sys.poke(0x0003, 0x19); // add hl,de + + cpu.set_adl(true); + cpu.state.reg.pc = 0; + cpu.state.reg.set8(Reg8::F, 0); + cpu.state.reg.set24(Reg16::HL, 0xffffff); + cpu.state.reg.set24(Reg16::DE, 0x000001); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + assert!(!cpu.state.reg.get_flag(Flag::Z)); + assert!(!cpu.state.reg.get_flag(Flag::C)); + + cpu.set_adl(false); + cpu.state.reg.pc = 0; + cpu.state.reg.set8(Reg8::F, 0); + cpu.state.reg.set24(Reg16::HL, 0xffffff); + cpu.state.reg.set24(Reg16::DE, 0x000001); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + assert!(!cpu.state.reg.get_flag(Flag::Z)); + assert!(!cpu.state.reg.get_flag(Flag::C)); + + cpu.set_adl(true); + cpu.state.reg.pc = 0; + cpu.state.reg.set8(Reg8::F, 0); + cpu.state.reg.set24(Reg16::HL, 0xfffffe); + cpu.state.reg.set24(Reg16::DE, 0xffffff); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + assert!(!cpu.state.reg.get_flag(Flag::Z)); + assert!(cpu.state.reg.get_flag(Flag::C)); + + cpu.set_adl(false); + cpu.state.reg.pc = 0; + cpu.state.reg.set8(Reg8::F, 0); + cpu.state.reg.set24(Reg16::HL, 0xfffffe); + cpu.state.reg.set24(Reg16::DE, 0xffffff); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + assert!(!cpu.state.reg.get_flag(Flag::Z)); + assert!(cpu.state.reg.get_flag(Flag::C)); + + cpu.set_adl(true); + cpu.state.reg.pc = 0; + cpu.state.reg.set8(Reg8::F, 0); + cpu.state.reg.set24(Reg16::HL, 0xfffffe); + cpu.state.reg.set24(Reg16::DE, 0xfffffe); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + assert!(cpu.state.reg.get_flag(Flag::Z)); + assert!(!cpu.state.reg.get_flag(Flag::C)); + + cpu.set_adl(false); + cpu.state.reg.pc = 0; + cpu.state.reg.set8(Reg8::F, 0); + cpu.state.reg.set24(Reg16::HL, 0xfffffe); + cpu.state.reg.set24(Reg16::DE, 0xfffffe); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + assert!(cpu.state.reg.get_flag(Flag::Z)); + assert!(!cpu.state.reg.get_flag(Flag::C)); +} + +#[test] +fn test_ld_sil() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + + // ld.sil (0x20005),hl + sys.poke(0x20000, 0x52); // ld.sil (0x20005),hl + sys.poke(0x20001, 0x22); + sys.poke(0x20002, 0x05); + sys.poke(0x20003, 0x00); + sys.poke(0x20004, 0x04); + + cpu.set_adl(true); + cpu.state.reg.pc = 0x20000; + cpu.state.reg.mbase = 1; + cpu.state.reg.set8(Reg8::F, 0); + cpu.state.reg.set24(Reg16::HL, 0xcafeba); + cpu.execute_instruction(&mut sys); + + assert_eq!(sys.peek(0x20005), 0); + assert_eq!(sys.peek(0x20006), 0); + assert_eq!(sys.peek(0x20007), 0); + + assert_eq!(sys.peek(0x10005), 0xba); + assert_eq!(sys.peek(0x10006), 0xfe); + assert_eq!(sys.peek(0x10007), 0x00); +} + +#[test] +fn test_ldir_cycles() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + + sys.poke(0x10000, 0xed); // ldir + sys.poke(0x10001, 0xb0); + sys.set_elapsed_cycles(0); + + cpu.set_adl(true); + cpu.state.reg.pc = 0x10000; + cpu.state.reg.set8(Reg8::F, 0); + cpu.state.reg.set24(Reg16::BC, 3); + cpu.state.reg.set24(Reg16::DE, 0xff); + cpu.state.reg.set24(Reg16::HL, 0x100); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + + assert_eq!(cpu.state.reg.pc, 0x10002); + assert_eq!(sys.get_elapsed_cycles(), 2 + 3 * 3); // 2 + 3*bc +} + +#[test] +fn test_ldir_lil_cycles() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + + sys.poke(0x0, 0x49); // ldir.l + sys.poke(0x1, 0xed); // ldir + sys.poke(0x2, 0xb0); + sys.set_elapsed_cycles(0); + + cpu.set_adl(true); + cpu.state.reg.pc = 0x0; + cpu.state.reg.set8(Reg8::F, 0); + cpu.state.reg.set24(Reg16::BC, 3); + cpu.state.reg.set24(Reg16::DE, 0xff); + cpu.state.reg.set24(Reg16::HL, 0x100); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + + assert_eq!(cpu.state.reg.pc, 0x3); + assert_eq!(sys.get_elapsed_cycles(), 1 + 2 + 3 * 3); +} + +#[test] +fn test_otirx() { + let mut sys = PlainMachine::new(); + let mut cpu = Cpu::new_ez80(); + + sys.poke(0x0, 0xed); // otirx + sys.poke(0x1, 0xc3); + sys.poke(0x100, 0xca); + sys.poke(0x101, 0xfe); + sys.poke(0x102, 0xba); + sys.set_elapsed_cycles(0); + + cpu.set_adl(true); + cpu.state.reg.pc = 0x0; + cpu.state.reg.set8(Reg8::F, 0); + cpu.state.reg.set24(Reg16::BC, 3); + cpu.state.reg.set24(Reg16::DE, 0xabcd); + cpu.state.reg.set24(Reg16::HL, 0x100); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + cpu.execute_instruction(&mut sys); + + assert_eq!(cpu.state.reg.pc, 0x2); + assert_eq!(sys.get_elapsed_cycles(), 2 + 3 * 3); +} diff --git a/tests/opcodes.rs b/tests/opcodes.rs index 7e6a607..8dfb2af 100644 --- a/tests/opcodes.rs +++ b/tests/opcodes.rs @@ -1,4 +1,4 @@ -use iz80::*; +use ez80::*; #[test] fn test_two_opcodes() { diff --git a/tests/opcodes_alu.rs b/tests/opcodes_alu.rs index a680e7d..2977836 100644 --- a/tests/opcodes_alu.rs +++ b/tests/opcodes_alu.rs @@ -1,4 +1,4 @@ -use iz80::*; +use ez80::*; #[test] fn test_cp_a() { diff --git a/tests/opcodes_arith.rs b/tests/opcodes_arith.rs index 65daae6..5541d4d 100644 --- a/tests/opcodes_arith.rs +++ b/tests/opcodes_arith.rs @@ -1,4 +1,4 @@ -use iz80::*; +use ez80::*; #[test] fn test_neg_a() { diff --git a/tests/opcodes_bits.rs b/tests/opcodes_bits.rs index 153e23e..5672465 100644 --- a/tests/opcodes_bits.rs +++ b/tests/opcodes_bits.rs @@ -1,4 +1,4 @@ -use iz80::*; +use ez80::*; #[test] fn test_rrca_fast() { diff --git a/tests/opcodes_io.rs b/tests/opcodes_io.rs index 20cca76..30febb5 100644 --- a/tests/opcodes_io.rs +++ b/tests/opcodes_io.rs @@ -1,4 +1,4 @@ -use iz80::*; +use ez80::*; #[test] fn test_out_e() { diff --git a/tests/opcodes_jumps.rs b/tests/opcodes_jumps.rs index a807e67..ae936ec 100644 --- a/tests/opcodes_jumps.rs +++ b/tests/opcodes_jumps.rs @@ -1,4 +1,4 @@ -use iz80::*; +use ez80::*; #[test] fn test_djnz_jump() { @@ -11,7 +11,7 @@ fn test_djnz_jump() { cpu.execute_instruction(&mut sys); assert_eq!(0x22, cpu.registers().get8(Reg8::B)); - assert_eq!(0x0008, cpu.registers().pc()); + assert_eq!(0x0008, cpu.state.pc()); } #[test] @@ -25,7 +25,7 @@ fn test_djnz_no_jump() { cpu.execute_instruction(&mut sys); assert_eq!(0x00, cpu.registers().get8(Reg8::B)); - assert_eq!(0x0002, cpu.registers().pc()); + assert_eq!(0x0002, cpu.state.pc()); } #[test] @@ -38,7 +38,7 @@ fn test_jr_z_jump() { cpu.registers().set_flag(Flag::Z); cpu.execute_instruction(&mut sys); - assert_eq!(0xFFFE, cpu.registers().pc()); + assert_eq!(0xFFFE, cpu.state.pc()); } #[test] @@ -51,7 +51,7 @@ fn test_jp() { sys.poke(0x0002, 0x20); cpu.execute_instruction(&mut sys); - assert_eq!(0x2000, cpu.registers().pc()); + assert_eq!(0x2000, cpu.state.pc()); } #[test] @@ -65,7 +65,7 @@ fn test_call() { cpu.execute_instruction(&mut sys); - assert_eq!(0x2000, cpu.registers().pc()); + assert_eq!(0x2000, cpu.state.pc()); //assert_eq!(0x0003, cpu.env.pop()); } @@ -80,7 +80,7 @@ fn test_call_z_jump() { cpu.registers().set_flag(Flag::Z); cpu.execute_instruction(&mut sys); - assert_eq!(0x2000, cpu.registers().pc()); + assert_eq!(0x2000, cpu.state.pc()); //assert_eq!(0x0003, cpu.env.pop()); } @@ -95,7 +95,7 @@ fn test_call_z_no_jump() { cpu.registers().clear_flag(Flag::Z); cpu.execute_instruction(&mut sys); - assert_eq!(0x0003, cpu.registers().pc()); + assert_eq!(0x0003, cpu.state.pc()); } #[test] @@ -106,7 +106,7 @@ fn test_rst() { sys.poke(0x0000, 0xff); // RST 38h cpu.execute_instruction(&mut sys); - assert_eq!(0x0038, cpu.registers().pc()); + assert_eq!(0x0038, cpu.state.pc()); //assert_eq!(0x0001, cpu.env.pop()); } @@ -122,7 +122,7 @@ fn test_call_ret() { sys.poke(0x2000, 0xc9); // RET cpu.execute_instruction(&mut sys); - assert_eq!(0x2000, cpu.registers().pc()); + assert_eq!(0x2000, cpu.state.pc()); cpu.execute_instruction(&mut sys); - assert_eq!(0x0003, cpu.registers().pc()); + assert_eq!(0x0003, cpu.state.pc()); } diff --git a/tests/opcodes_ld.rs b/tests/opcodes_ld.rs index f28a654..4130a29 100644 --- a/tests/opcodes_ld.rs +++ b/tests/opcodes_ld.rs @@ -1,4 +1,4 @@ -use iz80::*; +use ez80::*; #[test] fn test_ld_bc_nn() { @@ -48,7 +48,7 @@ fn test_ld_pnn_bc() { cpu.execute_instruction(&mut sys); - assert_eq!(0xde23, sys.peek16(0x1234)); + assert_eq!(0xde23, sys._peek16(0x1234)); } #[test] diff --git a/tests/z80test.rs b/tests/z80test.rs index 8511bcb..c3fa253 100644 --- a/tests/z80test.rs +++ b/tests/z80test.rs @@ -1,4 +1,4 @@ -use iz80::*; +use ez80::*; // From https://github.com/raxoft/z80test // Not passing @@ -22,7 +22,7 @@ fn z80test() { let code = CODE; let size = code.len(); for i in 0..size { - machine.poke(START + i as u16, code[i]); + machine.poke(START as u32 + i as u32, code[i]); } // Do nothing on 0x1601 and RST 0x10 @@ -33,28 +33,28 @@ fn z80test() { let run_single_test = false; let single_test = 148; if run_single_test { - machine.poke16(0x802b, single_test); // ld bc, 0 to ld bc, test - let mut test_start = machine.peek16(0x802e); + machine._poke16(0x802b, single_test); // ld bc, 0 to ld bc, test + let mut test_start = machine._peek16(0x802e); println!("Test table {:x}", test_start); test_start += single_test*2; println!("Test table {:x}", test_start); - machine.poke16(0x802e, test_start); // Move start - machine.poke16(test_start + 2 , 0); // NUL terminate test + machine._poke16(0x802e, test_start); // Move start + machine._poke16(test_start as u32 + 2 , 0); // NUL terminate test } - cpu.registers().set_pc(START); + cpu.state.set_pc(START as u32); let trace = false; cpu.set_trace(trace); let mut msg = String::new(); loop { cpu.execute_instruction(&mut machine); - if cpu.registers().pc() == 0x0000 { + if cpu.state.pc() == 0x0000 { println!(""); break; } - if cpu.registers().pc() == 0x0010 { + if cpu.state.pc() == 0x0010 { let mut ch = cpu.registers().get8(Reg8::A) as char; if ch == '\r' { ch = '\n' @@ -70,4 +70,4 @@ fn z80test() { } assert_eq!(true, msg.contains("CPU TESTS OK")); -} \ No newline at end of file +} diff --git a/tests/zexall.rs b/tests/zexall.rs index 52691d4..ae27c2f 100644 --- a/tests/zexall.rs +++ b/tests/zexall.rs @@ -1,4 +1,4 @@ -use iz80::*; +use ez80::*; //static ZEXDOC: &'static [u8] = include_bytes!("res/zexdoc.com"); static ZEXALL: &'static [u8] = include_bytes!("res/zexall.com"); @@ -14,7 +14,7 @@ fn test_zexall() { let code = ZEXALL; let size = code.len(); for i in 0..size { - machine.poke(0x100 + i as u16, code[i]); + machine.poke(0x100 + i as u32, code[i]); } /* @@ -26,21 +26,21 @@ fn test_zexall() { */ let code = [0xD3, 0x00, 0xC9]; for i in 0..code.len() { - machine.poke(5 + i as u16, code[i]); + machine.poke(5 + i as u32, code[i]); } // Patch to run a single test let run_single_test = false; let single_test = 11; if run_single_test { - let mut test_start = machine.peek16(0x0120); + let mut test_start = machine._peek16(0x0120); test_start += single_test*2; - machine.poke16(0x0120, test_start); - machine.poke16(test_start + 2 , 0); + machine._poke16(0x0120, test_start); + machine._poke16(test_start as u32 + 2 , 0); } - cpu.registers().set_pc(0x100); + cpu.state.set_pc(0x100); let trace = false; cpu.set_trace(trace); let mut tests_passed = 0; @@ -49,7 +49,7 @@ fn test_zexall() { if trace { // Test state - let addr = 0x1d80 as u16; + let addr = 0x1d80 as u32; print!("Zex state 0x{:04x}: ", addr); for i in 0..0x10 { print!("{:02x} ", machine.peek(addr + i)); @@ -57,12 +57,12 @@ fn test_zexall() { println!(""); } - if cpu.registers().pc() == 0x0000 { + if cpu.state.pc() == 0x0000 { println!(""); break; } - if cpu.registers().pc() == 0x0005 { + if cpu.state.pc() == 0x0005 { match cpu.registers().get8(Reg8::C) { 2 => { // C_WRITE @@ -73,7 +73,7 @@ fn test_zexall() { let mut address = cpu.registers().get16(Reg16::DE); let mut msg = String::new(); loop { - let ch = machine.peek(address) as char; + let ch = machine.peek(address as u32) as char; address += 1; if ch == '$'{ @@ -96,4 +96,4 @@ fn test_zexall() { } else { assert_eq!(67, tests_passed); } -} \ No newline at end of file +}