From 5539441aae4044cea1a26b9c76fce10178f5065b Mon Sep 17 00:00:00 2001 From: dbalsom Date: Sat, 4 May 2024 20:38:02 -0400 Subject: [PATCH 01/11] Add CpuDispatch and Cpu trait --- Cargo.lock | 25 +- Cargo.toml | 1 + core/Cargo.toml | 1 + core/src/bus.rs | 22 +- core/src/cpu_808x/addressing.rs | 2 +- core/src/cpu_808x/alu.rs | 2 +- core/src/cpu_808x/bcd.rs | 2 +- core/src/cpu_808x/bitwise.rs | 30 +- core/src/cpu_808x/biu.rs | 15 +- core/src/cpu_808x/cpu.rs | 474 ++++++++++++++++++ core/src/cpu_808x/cycle.rs | 2 +- core/src/cpu_808x/decode.rs | 7 +- core/src/cpu_808x/display.rs | 9 +- core/src/cpu_808x/execute.rs | 12 +- core/src/cpu_808x/fuzzer.rs | 6 +- core/src/cpu_808x/instruction.rs | 50 +- core/src/cpu_808x/interrupt.rs | 2 +- core/src/cpu_808x/jump.rs | 2 +- core/src/cpu_808x/logging.rs | 5 +- core/src/cpu_808x/mod.rs | 348 ++----------- core/src/cpu_808x/muldiv.rs | 15 +- core/src/cpu_808x/stack.rs | 16 +- core/src/cpu_808x/step.rs | 10 +- core/src/cpu_808x/string.rs | 2 +- core/src/cpu_common/builder.rs | 136 +++++ core/src/cpu_common/mod.rs | 220 +++++++- core/src/cpu_validator.rs | 3 +- core/src/machine.rs | 70 ++- core/src/machine_config.rs | 6 +- .../src/cpu_test/common.rs | 7 +- .../src/cpu_test/run_tests.rs | 68 ++- .../martypc_desktop_wgpu/src/emulator/mod.rs | 4 +- .../src/event_loop/egui_events.rs | 15 +- .../src/event_loop/egui_update.rs | 8 +- .../martypc_desktop_wgpu/src/run_benchmark.rs | 8 +- lib/frontend/config_toml_bpaf/src/lib.rs | 9 +- .../src/windows/cpu_state_viewer.rs | 2 +- 37 files changed, 1104 insertions(+), 512 deletions(-) create mode 100644 core/src/cpu_808x/cpu.rs create mode 100644 core/src/cpu_common/builder.rs diff --git a/Cargo.lock b/Cargo.lock index 157ef912..09165ceb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,9 +162,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anyhow" @@ -243,9 +243,9 @@ checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" @@ -1226,6 +1226,18 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "enumn" version = "0.1.13" @@ -2080,6 +2092,7 @@ dependencies = [ "const_format", "cpal", "criterion", + "enum_dispatch", "fxhash", "lazy_static", "log", @@ -2578,9 +2591,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] diff --git a/Cargo.toml b/Cargo.toml index 50b454ae..fc388fb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ serialport = { git = "https://github.com/dbalsom/serialport-rs", branch = "ardui web-time = "0.2.4" toml = "0.8" fxhash = "0.2.1" +enum_dispatch = "0.3.13" [workspace.dependencies.image] version = "0.24" diff --git a/core/Cargo.toml b/core/Cargo.toml index 4b1187f7..5223f840 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -31,6 +31,7 @@ strum_macros = "0.26" toml = "0.5.10" uuid = { version = "1.1.2", features = ["v4"] } fxhash.workspace = true +enum_dispatch.workspace = true [dev-dependencies] criterion = "0.5" diff --git a/core/src/bus.rs b/core/src/bus.rs index 2c1d2784..bc446758 100644 --- a/core/src/bus.rs +++ b/core/src/bus.rs @@ -79,6 +79,7 @@ use crate::devices::ega::{self, EGACard}; #[cfg(feature = "vga")] use crate::devices::vga::{self, VGACard}; use crate::{ + cpu_common::{CpuDispatch, CpuType}, device_traits::videocard::VideoCardSubType, devices::{a0::A0Register, lpt_card::ParallelController, tga, tga::TGACard}, machine_types::{FdcType, MachineType}, @@ -1520,6 +1521,23 @@ impl BusInterface { } } + pub fn dump_mem_range(&self, start: u32, end: u32, path: &Path) { + let filename = path.to_path_buf(); + + let len = end.saturating_sub(start) as usize; + let end = (start as usize + len) & 0xFFFFF; + log::debug!("Dumping {} bytes at address {:05X}", len, start); + + match std::fs::write(filename.clone(), &self.memory[(start as usize)..=end]) { + Ok(_) => { + log::debug!("Wrote memory dump: {}", filename.display()) + } + Err(e) => { + log::error!("Failed to write memory dump '{}': {}", filename.display(), e) + } + } + } + pub fn dump_ivt_tokens(&mut self) -> Vec> { let mut vec: Vec> = Vec::new(); @@ -1563,7 +1581,7 @@ impl BusInterface { /// Returns a MemoryDebug struct containing information about the memory at the specified address. /// This is used in the Memory Viewer debug window to show a popup when hovering over a byte. - pub fn get_memory_debug(&mut self, address: usize) -> MemoryDebug { + pub fn get_memory_debug(&mut self, cpu_type: CpuType, address: usize) -> MemoryDebug { let mut debug = MemoryDebug { addr: format!("{:05X}", address), byte: String::new(), @@ -1593,7 +1611,7 @@ impl BusInterface { self.seek(address); - debug.instr = match Cpu::decode(self, true) { + debug.instr = match cpu_type.decode(self, true) { Ok(instruction) => { format!("{}", instruction) } diff --git a/core/src/cpu_808x/addressing.rs b/core/src/cpu_808x/addressing.rs index b996dd05..42df2b76 100644 --- a/core/src/cpu_808x/addressing.rs +++ b/core/src/cpu_808x/addressing.rs @@ -68,7 +68,7 @@ pub enum FarPtr { } #[rustfmt::skip] -impl Cpu { +impl Intel808x { #[allow(dead_code)] #[inline] fn is_register_mode(mode: AddressingMode) -> bool { diff --git a/core/src/cpu_808x/alu.rs b/core/src/cpu_808x/alu.rs index 13b6de15..78adf2c3 100644 --- a/core/src/cpu_808x/alu.rs +++ b/core/src/cpu_808x/alu.rs @@ -71,7 +71,7 @@ pub enum Xi { //use num_traits::PrimInt; -impl Cpu { +impl Intel808x { #[inline(always)] fn set_parity_flag_from_u8(&mut self, operand: u8) { self.set_flag_state(Flag::Parity, PARITY_TABLE[operand as usize]); diff --git a/core/src/cpu_808x/bcd.rs b/core/src/cpu_808x/bcd.rs index 3cb1d76f..330b5418 100644 --- a/core/src/cpu_808x/bcd.rs +++ b/core/src/cpu_808x/bcd.rs @@ -32,7 +32,7 @@ use crate::cpu_808x::{muldiv::*, *}; -impl Cpu { +impl Intel808x { /// Ascii Adjust after Addition /// Flags: AuxCarry and Carry are set per operation. The OF, SF, ZF, and PF flags are undefined. pub fn aaa(&mut self) { diff --git a/core/src/cpu_808x/bitwise.rs b/core/src/cpu_808x/bitwise.rs index 6c935379..2711666c 100644 --- a/core/src/cpu_808x/bitwise.rs +++ b/core/src/cpu_808x/bitwise.rs @@ -32,7 +32,7 @@ use crate::cpu_808x::{mnemonic::Mnemonic, *}; -impl Cpu { +impl Intel808x { pub(crate) fn shl_u8_with_carry(mut byte: u8, mut count: u8) -> (u8, bool) { let mut carry = false; while count > 0 { @@ -242,7 +242,7 @@ impl Cpu { match opcode { Mnemonic::ROL => { - (result, carry) = Cpu::rol_u8_with_carry(operand1, rot_count); + (result, carry) = Intel808x::rol_u8_with_carry(operand1, rot_count); self.set_flag_state(Flag::Carry, carry); // Only set overflow on ROL of 1 if rot_count == 1 { @@ -251,7 +251,7 @@ impl Cpu { } } Mnemonic::ROR => { - (result, carry) = Cpu::ror_u8_with_carry(operand1, rot_count); + (result, carry) = Intel808x::ror_u8_with_carry(operand1, rot_count); self.set_flag_state(Flag::Carry, carry); // Only set overflow on ROR of 1 if rot_count == 1 { @@ -264,7 +264,7 @@ impl Cpu { // Flags: For left rotates, the OF flag is set to the exclusive OR of the CF bit (after the rotate) // and the most-significant bit of the result. let existing_carry = self.get_flag(Flag::Carry); - (result, carry) = Cpu::rcl_u8_with_carry(operand1, rot_count, existing_carry); + (result, carry) = Intel808x::rcl_u8_with_carry(operand1, rot_count, existing_carry); self.set_flag_state(Flag::Carry, carry); // Only set overflow on SHL of 1 if rot_count == 1 { @@ -280,7 +280,7 @@ impl Cpu { self.set_flag_state(Flag::Overflow, ((operand1 & 0x80) != 0) ^ existing_carry); } - (result, carry) = Cpu::rcr_u8_with_carry(operand1, rot_count, existing_carry); + (result, carry) = Intel808x::rcr_u8_with_carry(operand1, rot_count, existing_carry); self.set_flag_state(Flag::Carry, carry); } Mnemonic::SETMO => { @@ -310,7 +310,7 @@ impl Cpu { } } Mnemonic::SHL => { - (result, carry) = Cpu::shl_u8_with_carry(operand1, operand2); + (result, carry) = Intel808x::shl_u8_with_carry(operand1, operand2); // Set state of Carry Flag self.set_flag_state(Flag::Carry, carry); @@ -324,7 +324,7 @@ impl Cpu { self.set_szp_flags_from_result_u8(result); } Mnemonic::SHR => { - (result, carry) = Cpu::shr_u8_with_carry(operand1, operand2); + (result, carry) = Intel808x::shr_u8_with_carry(operand1, operand2); // Set state of Carry Flag self.set_flag_state(Flag::Carry, carry); @@ -337,7 +337,7 @@ impl Cpu { self.set_szp_flags_from_result_u8(result); } Mnemonic::SAR => { - (result, carry) = Cpu::sar_u8_with_carry(operand1, operand2); + (result, carry) = Intel808x::sar_u8_with_carry(operand1, operand2); // Set Carry Flag self.set_flag_state(Flag::Carry, carry); @@ -381,7 +381,7 @@ impl Cpu { // Rotate Left // Flags: For left rotates, the OF flag is set to the exclusive OR of the CF bit (after the rotate) // and the most-significant bit of the result. - (result, carry) = Cpu::rol_u16_with_carry(operand1, rot_count); + (result, carry) = Intel808x::rol_u16_with_carry(operand1, rot_count); self.set_flag_state(Flag::Carry, carry); // Overflow only defined for ROL of 1 @@ -393,7 +393,7 @@ impl Cpu { Mnemonic::ROR => { // Rotate Right // Flags: For right rotates, the OF flag is set to the exclusive OR of the two most-significant bits of the result. - (result, carry) = Cpu::ror_u16_with_carry(operand1, rot_count); + (result, carry) = Intel808x::ror_u16_with_carry(operand1, rot_count); self.set_flag_state(Flag::Carry, carry); // Overflow only defined for ROR of 1 @@ -408,7 +408,7 @@ impl Cpu { // and the most-significant bit of the result. let existing_carry = self.get_flag(Flag::Carry); - (result, carry) = Cpu::rcl_u16_with_carry(operand1, rot_count, existing_carry); + (result, carry) = Intel808x::rcl_u16_with_carry(operand1, rot_count, existing_carry); self.set_flag_state(Flag::Carry, carry); // Overflow only defined for RCL of 1 if rot_count == 1 { @@ -429,7 +429,7 @@ impl Cpu { self.set_flag_state(Flag::Overflow, ((operand1 & 0x8000) != 0) ^ existing_carry); } - (result, carry) = Cpu::rcr_u16_with_carry(operand1, rot_count, existing_carry); + (result, carry) = Intel808x::rcr_u16_with_carry(operand1, rot_count, existing_carry); self.set_flag_state(Flag::Carry, carry); // The rcr instruction does not affect the zero, sign, parity, or auxiliary carry flags. @@ -462,7 +462,7 @@ impl Cpu { } } Mnemonic::SHL => { - (result, carry) = Cpu::shl_u16_with_carry(operand1, operand2); + (result, carry) = Intel808x::shl_u16_with_carry(operand1, operand2); // Set state of Carry Flag self.set_flag_state(Flag::Carry, carry); @@ -478,7 +478,7 @@ impl Cpu { self.set_szp_flags_from_result_u16(result); } Mnemonic::SHR => { - (result, carry) = Cpu::shr_u16_with_carry(operand1, operand2); + (result, carry) = Intel808x::shr_u16_with_carry(operand1, operand2); // Set state of Carry Flag self.set_flag_state(Flag::Carry, carry); @@ -491,7 +491,7 @@ impl Cpu { self.set_szp_flags_from_result_u16(result); } Mnemonic::SAR => { - (result, carry) = Cpu::sar_u16_with_carry(operand1, operand2); + (result, carry) = Intel808x::sar_u16_with_carry(operand1, operand2); // Set Carry Flag self.set_flag_state(Flag::Carry, carry); diff --git a/core/src/cpu_808x/biu.rs b/core/src/cpu_808x/biu.rs index 1b72ccba..27987d9e 100644 --- a/core/src/cpu_808x/biu.rs +++ b/core/src/cpu_808x/biu.rs @@ -39,7 +39,7 @@ pub enum ReadWriteFlag { RNI, } -impl ByteQueue for Cpu { +impl ByteQueue for Intel808x { fn seek(&mut self, _pos: usize) { // Instruction queue does not support seeking } @@ -117,7 +117,7 @@ impl ByteQueue for Cpu { } } -impl Cpu { +impl Intel808x { /// Read a byte from the instruction queue. /// Either return a byte currently in the queue, or fetch a byte into the queue and /// then return it. @@ -316,12 +316,15 @@ impl Cpu { } pub fn biu_queue_has_room(&mut self) -> bool { - match self.cpu_type { - CpuType::Intel8088 | CpuType::Harris80C88 => self.queue.len() < QUEUE_SIZE, - CpuType::Intel8086 => { + match self.cpu_subtype { + CpuSubType::Intel8088 | CpuSubType::Harris80C88 => self.queue.len() < QUEUE_SIZE, + CpuSubType::Intel8086 => { // 8086 fetches two bytes at a time, so must be two free bytes in queue self.queue.len() < QUEUE_SIZE - 1 } + _ => { + panic!("Unsupported CPU subtype") + } } } @@ -920,7 +923,7 @@ impl Cpu { } pub fn biu_fetch_bus_begin(&mut self) { - let addr = Cpu::calc_linear_address(self.cs, self.pc); + let addr = Intel808x::calc_linear_address(self.cs, self.pc); if self.biu_queue_has_room() { //trace_print!(self, "Setting address bus to PC: {:05X}", self.pc); self.fetch_state = FetchState::Normal; diff --git a/core/src/cpu_808x/cpu.rs b/core/src/cpu_808x/cpu.rs new file mode 100644 index 00000000..01342af7 --- /dev/null +++ b/core/src/cpu_808x/cpu.rs @@ -0,0 +1,474 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_808x::cpu.rs + + Implements the Cpu trait for the Intel 808x CPUs + +*/ + +use crate::{ + breakpoints::{BreakPointType, StopWatchData}, + bus::BusInterface, + bytequeue::ByteQueue, + cpu_808x::{ + trace_print, + BusStatus, + CpuAddress, + CpuError, + CpuState, + FetchState, + Intel808x, + QueueOp, + Register16, + ServiceEvent, + StepResult, + TCycle, + TaCycle, + CPU_FLAGS_RESERVED_ON, + }, + cpu_common::{Cpu, CpuDispatch, CpuOption, CpuStringState, CpuType, Instruction, TraceMode}, + cpu_validator::{CycleState, VRegisters}, + syntax_token::SyntaxToken, +}; + +#[cfg(feature = "cpu_validator")] +use crate::cpu_808x::CpuValidatorState; + +impl Cpu for Intel808x { + fn reset(&mut self) { + log::debug!("CPU Resetting..."); + /* + let trace_logger = std::mem::replace(&mut self.trace_logger, TraceLogger::None); + + // Save non-default values + *self = Self { + // Save parameters to new() + cpu_type: self.cpu_type, + reset_vector: self.reset_vector, + trace_mode: self.trace_mode, + trace_logger, + // Save options + instruction_history_on: self.instruction_history_on, + dram_refresh_simulation: self.dram_refresh_simulation, + halt_resume_delay: self.halt_resume_delay, + off_rails_detection: self.off_rails_detection, + enable_wait_states: self.enable_wait_states, + trace_enabled: self.trace_enabled, + + // Copy bus + bus: self.bus, + + #[cfg(feature = "cpu_validator")] + validator_type: ValidatorType, + #[cfg(feature = "cpu_validator")] + validator_trace: TraceLogger, + ..Self::default() + }; + */ + + self.state = CpuState::Normal; + + self.set_register16(Register16::AX, 0); + self.set_register16(Register16::BX, 0); + self.set_register16(Register16::CX, 0); + self.set_register16(Register16::DX, 0); + self.set_register16(Register16::SP, 0); + self.set_register16(Register16::BP, 0); + self.set_register16(Register16::SI, 0); + self.set_register16(Register16::DI, 0); + self.set_register16(Register16::ES, 0); + + self.set_register16(Register16::SS, 0); + self.set_register16(Register16::DS, 0); + + self.flags = CPU_FLAGS_RESERVED_ON; + + self.queue.flush(); + + if let CpuAddress::Segmented(segment, offset) = self.reset_vector { + self.set_register16(Register16::CS, segment); + self.set_register16(Register16::PC, offset); + } + else { + panic!("Invalid CpuAddress for reset vector."); + } + + self.address_latch = 0; + self.bus_status = BusStatus::Passive; + self.bus_status_latch = BusStatus::Passive; + self.t_cycle = TCycle::Ti; + self.ta_cycle = TaCycle::Td; + self.pl_status = BusStatus::Passive; + self.pl_slot = false; + + self.fetch_state = FetchState::Normal; + + self.instruction_count = 0; + self.int_count = 0; + self.iret_count = 0; + self.instr_cycle = 0; + self.cycle_num = 1; + self.halt_cycles = 0; + self.t_stamp = 0.0; + self.t_step = 0.00000021; + self.t_step_h = 0.000000105; + self.ready = true; + self.in_rep = false; + self.halted = false; + self.reported_halt = false; + self.halt_not_hold = false; + self.opcode0_counter = 0; + self.interrupt_inhibit = false; + self.intr_pending = false; + self.in_int = false; + self.is_error = false; + self.instruction_history.clear(); + self.call_stack.clear(); + self.int_flags = vec![0; 256]; + + self.instruction_reentrant = false; + self.last_ip = 0; + self.last_cs = 0; + self.last_intr = false; + self.jumped = false; + + self.queue_op = QueueOp::Idle; + self.last_queue_op = QueueOp::Idle; + + self.i8288.ale = false; + self.i8288.mrdc = false; + self.i8288.amwc = false; + self.i8288.mwtc = false; + self.i8288.iorc = false; + self.i8288.aiowc = false; + self.i8288.iowc = false; + + self.dram_refresh_tc = false; + self.dram_refresh_retrigger = false; + + self.step_over_target = None; + self.step_over_breakpoint = None; + self.end_addr = 0xFFFFF; + self.stopwatch_running = false; + + self.nx = false; + self.rni = false; + + self.halt_resume_delay = 4; + + // Reset takes 6 cycles before first fetch + self.cycle(); + self.biu_fetch_suspend(); + self.cycles_i(2, &[0x1e4, 0x1e5]); + self.biu_queue_flush(); + self.cycles_i(3, &[0x1e6, 0x1e7, 0x1e8]); + + #[cfg(feature = "cpu_validator")] + { + self.validator_state = CpuValidatorState::Uninitialized; + self.cycle_states.clear(); + } + + trace_print!(self, "Reset CPU! CS: {:04X} IP: {:04X}", self.cs, self.ip()); + } + + #[inline] + fn set_reset_vector(&mut self, address: CpuAddress) { + self.set_reset_vector(address); + } + + #[inline] + fn set_end_address(&mut self, address: CpuAddress) { + let end_addr; + match address { + CpuAddress::Segmented(segment, offset) => { + end_addr = Intel808x::calc_linear_address(segment, offset); + } + CpuAddress::Flat(addr) => { + end_addr = addr; + } + _ => { + panic!("Invalid CpuAddress for end address."); + } + } + self.set_end_address(end_addr as usize); + } + + #[inline] + fn set_nmi(&mut self, state: bool) { + self.set_nmi(state); + } + + #[inline] + fn set_intr(&mut self, state: bool) { + self.set_intr(state); + } + + #[inline] + fn step(&mut self, skip_breakpoint: bool) -> Result<(StepResult, u32), CpuError> { + self.step(skip_breakpoint) + } + + #[inline] + fn step_finish(&mut self) -> Result { + self.step_finish() + } + + #[inline] + fn in_rep(&self) -> bool { + self.in_rep + } + + #[inline] + fn get_type(&self) -> CpuType { + self.cpu_type + } + + #[inline] + fn get_ip(&mut self) -> u16 { + self.ip() + } + + #[inline] + fn get_register16(&self, reg: Register16) -> u16 { + self.get_register16(reg) + } + + #[inline] + fn set_register16(&mut self, reg: Register16, value: u16) { + self.set_register16(reg, value); + } + + #[inline] + fn get_flags(&self) -> u16 { + self.get_flags() + } + + #[inline] + fn set_flags(&mut self, flags: u16) { + self.set_flags(flags); + } + + #[inline] + fn get_cycle_ct(&self) -> (u64, u64) { + self.get_cycle_ct() + } + + #[inline] + fn get_instruction_ct(&self) -> u64 { + self.get_instruction_ct() + } + + /// Return the resolved flat address of CS:CORR(PC) + #[inline] + fn flat_ip(&self) -> u32 { + Intel808x::calc_linear_address(self.cs, self.ip()) + } + + /// Return the resolved flat address of CS:CORR(PC), adjusted for reentrant instructions + #[inline] + fn flat_ip_disassembly(&self) -> u32 { + Intel808x::calc_linear_address(self.cs, self.disassembly_ip()) + } + + #[inline] + fn flat_sp(&self) -> u32 { + self.flat_sp() + } + + #[inline] + fn dump_instruction_history_string(&self) -> String { + self.dump_instruction_history_string() + } + + #[inline] + fn dump_instruction_history_tokens(&self) -> Vec> { + self.dump_instruction_history_tokens() + } + + fn dump_call_stack(&self) -> String { + self.dump_call_stack() + } + + #[inline] + fn get_service_event(&mut self) -> Option { + self.service_events.pop_front() + } + + #[inline] + fn get_cycle_states(&self) -> &Vec { + self.get_cycle_states() + } + + fn get_cycle_trace(&self) -> &Vec { + self.get_cycle_trace() + } + + fn get_cycle_trace_tokens(&self) -> &Vec> { + self.get_cycle_trace_tokens() + } + + #[inline] + #[cfg(feature = "cpu_validator")] + fn get_vregisters(&self) -> VRegisters { + self.get_vregisters() + } + + #[inline] + fn get_string_state(&self) -> CpuStringState { + self.get_string_state() + } + + fn eval_address(&self, expr: &str) -> Option { + self.eval_address(expr) + } + + #[inline] + fn clear_breakpoint_flag(&mut self) { + self.clear_breakpoint_flag(); + } + + #[inline] + fn set_breakpoints(&mut self, bp_list: Vec) { + self.set_breakpoints(bp_list) + } + + #[inline] + fn get_step_over_breakpoint(&self) -> Option { + self.get_step_over_breakpoint() + } + + #[inline] + fn set_step_over_breakpoint(&mut self, address: CpuAddress) { + self.set_step_over_breakpoint(address) + } + + #[inline] + fn get_sw_data(&self) -> Vec { + self.get_sw_data() + } + + #[inline] + fn set_stopwatch(&mut self, sw_idx: usize, start: u32, stop: u32) { + self.set_stopwatch(sw_idx, start, stop) + } + + fn set_option(&mut self, opt: CpuOption) { + match opt { + CpuOption::InstructionHistory(state) => { + log::debug!("Setting InstructionHistory to: {:?}", state); + self.instruction_history.clear(); + self.instruction_history_on = state; + } + CpuOption::ScheduleInterrupt(_state, cycle_target, cycles, retrigger) => { + log::debug!("Setting InterruptHint to: ({},{})", cycle_target, cycles); + self.interrupt_scheduling = true; + self.interrupt_cycle_period = cycle_target; + self.interrupt_cycle_num = cycles; + self.interrupt_retrigger = retrigger; + } + CpuOption::ScheduleDramRefresh(state, cycle_target, cycles, retrigger) => { + log::trace!( + "Setting SimulateDramRefresh to: {:?} ({},{})", + state, + cycle_target, + cycles + ); + self.dram_refresh_simulation = state; + self.dram_refresh_cycle_period = cycle_target; + self.dram_refresh_cycle_num = cycles; + self.dram_refresh_retrigger = retrigger; + self.dram_refresh_tc = false; + } + CpuOption::DramRefreshAdjust(adj) => { + log::debug!("Setting DramRefreshAdjust to: {}", adj); + self.dram_refresh_adjust = adj; + } + CpuOption::HaltResumeDelay(delay) => { + log::debug!("Setting HaltResumeDelay to: {}", delay); + self.halt_resume_delay = delay; + } + CpuOption::OffRailsDetection(state) => { + log::debug!("Setting OffRailsDetection to: {:?}", state); + self.off_rails_detection = state; + } + CpuOption::EnableWaitStates(state) => { + log::debug!("Setting EnableWaitStates to: {:?}", state); + self.enable_wait_states = state; + } + CpuOption::TraceLoggingEnabled(state) => { + log::debug!("Setting TraceLoggingEnabled to: {:?}", state); + self.trace_enabled = state; + + // Flush the trace log file on stopping trace so that we can immediately + // see results otherwise buffered + if state == false { + self.trace_flush(); + } + } + CpuOption::EnableServiceInterrupt(state) => { + log::debug!("Setting EnableServiceInterrupt to: {:?}", state); + self.enable_service_interrupt = state; + } + } + } + + fn get_option(&self, opt: CpuOption) -> bool { + match opt { + CpuOption::InstructionHistory(_) => self.instruction_history_on, + CpuOption::ScheduleInterrupt(..) => self.interrupt_cycle_period > 0, + CpuOption::ScheduleDramRefresh(..) => self.dram_refresh_simulation, + CpuOption::DramRefreshAdjust(..) => true, + CpuOption::HaltResumeDelay(..) => true, + CpuOption::OffRailsDetection(_) => self.off_rails_detection, + CpuOption::EnableWaitStates(_) => self.enable_wait_states, + CpuOption::TraceLoggingEnabled(_) => self.trace_enabled, + CpuOption::EnableServiceInterrupt(_) => self.enable_service_interrupt, + } + } + + fn bus(&self) -> &BusInterface { + &self.bus + } + + fn bus_mut(&mut self) -> &mut BusInterface { + &mut self.bus + } + + // Logging methods + fn cycle_table_header(&self) -> Vec { + self.cycle_table_header() + } + + fn emit_header(&mut self) { + self.emit_header(); + } + + fn trace_flush(&mut self) { + self.trace_flush(); + } +} diff --git a/core/src/cpu_808x/cycle.rs b/core/src/cpu_808x/cycle.rs index 9a618633..8d79dbc4 100644 --- a/core/src/cpu_808x/cycle.rs +++ b/core/src/cpu_808x/cycle.rs @@ -55,7 +55,7 @@ macro_rules! validate_write_u8 { }}; } -impl Cpu { +impl Intel808x { #[inline(always)] pub fn set_mc_pc(&mut self, instr: u16) { self.mc_pc = instr; diff --git a/core/src/cpu_808x/decode.rs b/core/src/cpu_808x/decode.rs index 422d96af..49ca62e5 100644 --- a/core/src/cpu_808x/decode.rs +++ b/core/src/cpu_808x/decode.rs @@ -41,7 +41,8 @@ use crate::cpu_808x::{addressing::AddressingMode, mnemonic::Mnemonic, modrm::Mod use crate::{ bytequeue::*, - cpu_808x::{alu::Xi, gdr::GdrEntry, instruction::Instruction}, + cpu_808x::{alu::Xi, gdr::GdrEntry}, + cpu_common::Instruction, }; #[derive(Copy, Clone, PartialEq)] @@ -506,7 +507,7 @@ pub const DECODE: [InstTemplate; 352] = [ inst!( 0xFF, 5, 0b0000100000100100, 0x026, PUSH , Ot::ModRM16, Ot::NoOperand), ]; -impl Cpu { +impl Intel808x { #[rustfmt::skip] pub fn decode(bytes: &mut impl ByteQueue, peek: bool) -> Result> { @@ -514,7 +515,7 @@ impl Cpu { let mut operand2_type: OperandType = OperandType::NoOperand; let mut operand1_size: OperandSize = OperandSize::NoOperand; let mut operand2_size: OperandSize = OperandSize::NoOperand; - + let mut opcode = bytes.q_read_u8(QueueType::First, QueueReader::Biu); let mut size: u32 = 1; let mut op_prefixes: u32 = 0; diff --git a/core/src/cpu_808x/display.rs b/core/src/cpu_808x/display.rs index 22303265..0464f56b 100644 --- a/core/src/cpu_808x/display.rs +++ b/core/src/cpu_808x/display.rs @@ -33,7 +33,10 @@ use std::fmt; -use crate::cpu_808x::{addressing::AddressingMode, instruction::Instruction, mnemonic::Mnemonic, *}; +use crate::{ + cpu_808x::{addressing::AddressingMode, mnemonic::Mnemonic, *}, + cpu_common::Instruction, +}; use crate::syntax_token::SyntaxToken; @@ -302,7 +305,7 @@ impl fmt::Display for Instruction { } } -impl Cpu { +impl Intel808x { pub fn tokenize_instruction(i: &Instruction) -> Vec { // Dont sign-extend 8-bit port addresses. let op_size = match i.mnemonic { @@ -344,7 +347,7 @@ impl Cpu { impl SyntaxTokenize for Instruction { fn tokenize(&self) -> Vec { - Cpu::tokenize_instruction(self) + Intel808x::tokenize_instruction(self) } } diff --git a/core/src/cpu_808x/execute.rs b/core/src/cpu_808x/execute.rs index 3a012738..a116ec67 100644 --- a/core/src/cpu_808x/execute.rs +++ b/core/src/cpu_808x/execute.rs @@ -76,7 +76,7 @@ macro_rules! alu_op { // rustfmt chokes on large match statements. #[rustfmt::skip] -impl Cpu { +impl Intel808x { /// Execute the current instruction. At the phase this function is called we have /// fetched and decoded any prefixes, the opcode byte, modrm and any displacement /// and populated an Instruction struct. @@ -1410,13 +1410,13 @@ impl Cpu { if self.intr { // If an intr is pending now, execute it without actually halting. - log::trace!("Halt overriden at [{:05X}]", Cpu::calc_linear_address(self.cs, self.ip())); + log::trace!("Halt overriden at [{:05X}]", Intel808x::calc_linear_address(self.cs, self.ip())); self.cycles(2); // Cycle to load interrupt routine self.halt_not_hold = false; } else { // Actually halt - log::trace!("Halt at [{:05X}]", Cpu::calc_linear_address(self.cs, self.ip())); + log::trace!("Halt at [{:05X}]", Intel808x::calc_linear_address(self.cs, self.ip())); self.halted = true; self.biu_halt(); // HLT is reentrant as step will remain in halt state until interrupt, even @@ -1744,7 +1744,7 @@ impl Cpu { self.biu_queue_flush(); // If this form uses a register operand, the full 16 bits are copied to IP. - self.pc = self.get_register16(Cpu::reg8to16(reg)); + self.pc = self.get_register16(Intel808x::reg8to16(reg)); } jump = true; } @@ -1796,7 +1796,7 @@ impl Cpu { self.biu_queue_flush(); // If this form uses a register operand, the full 16 bits are copied to PC. - self.pc = self.get_register16(Cpu::reg8to16(reg)); + self.pc = self.get_register16(Intel808x::reg8to16(reg)); } } // Jump to memory r/m16 @@ -1840,7 +1840,7 @@ impl Cpu { self.biu_queue_flush(); // If this form uses a register operand, the full 16 bits are copied to PC. - self.pc = self.get_register16(Cpu::reg8to16(reg)); + self.pc = self.get_register16(Intel808x::reg8to16(reg)); } } // Push Byte onto stack diff --git a/core/src/cpu_808x/fuzzer.rs b/core/src/cpu_808x/fuzzer.rs index 4ef4b836..76b22659 100644 --- a/core/src/cpu_808x/fuzzer.rs +++ b/core/src/cpu_808x/fuzzer.rs @@ -49,7 +49,7 @@ macro_rules! get_rand_range { }; } -impl Cpu { +impl Intel808x { #[allow(dead_code)] pub fn randomize_seed(&mut self, mut seed: u64) { if seed == 0 { @@ -254,7 +254,7 @@ impl Cpu { } // Copy instruction to memory at CS:IP - let addr = Cpu::calc_linear_address(self.cs, self.pc); + let addr = Intel808x::calc_linear_address(self.cs, self.pc); log::debug!("Using instruction vector: {:X?}", instr.make_contiguous()); self.bus .copy_from(instr.make_contiguous(), (addr & 0xFFFFF) as usize, 0, false) @@ -359,7 +359,7 @@ impl Cpu { } // Copy instruction to memory at CS:IP - let addr = Cpu::calc_linear_address(self.cs, self.pc); + let addr = Intel808x::calc_linear_address(self.cs, self.pc); log::debug!("Using instruction vector: {:X?}", instr.make_contiguous()); self.bus .copy_from(instr.make_contiguous(), addr as usize, 0, false) diff --git a/core/src/cpu_808x/instruction.rs b/core/src/cpu_808x/instruction.rs index 05d3973c..c24ffc25 100644 --- a/core/src/cpu_808x/instruction.rs +++ b/core/src/cpu_808x/instruction.rs @@ -30,48 +30,18 @@ */ -use crate::cpu_808x::{ - decode::{InstTemplate, DECODE}, - gdr::GdrEntry, - mnemonic::Mnemonic, - OperandSize, - OperandType, - Segment, +use crate::{ + cpu_808x::{ + decode::{InstTemplate, DECODE}, + gdr::GdrEntry, + mnemonic::Mnemonic, + OperandSize, + OperandType, + Segment, + }, + cpu_common::Instruction, }; -#[derive(Clone)] -pub struct Instruction { - pub decode_idx: usize, - pub opcode: u8, - pub prefixes: u32, - pub address: u32, - pub size: u32, - pub mnemonic: Mnemonic, - pub segment_override: Option, - pub operand1_type: OperandType, - pub operand1_size: OperandSize, - pub operand2_type: OperandType, - pub operand2_size: OperandSize, -} - -impl Default for Instruction { - fn default() -> Self { - Self { - decode_idx: 0, - opcode: 0, - prefixes: 0, - address: 0, - size: 1, - mnemonic: Mnemonic::NOP, - segment_override: None, - operand1_type: OperandType::NoOperand, - operand1_size: OperandSize::NoOperand, - operand2_type: OperandType::NoOperand, - operand2_size: OperandSize::NoOperand, - } - } -} - impl Instruction { #[inline(always)] pub fn decode_ref(&self) -> &InstTemplate { diff --git a/core/src/cpu_808x/interrupt.rs b/core/src/cpu_808x/interrupt.rs index d53926c5..6bb54ae8 100644 --- a/core/src/cpu_808x/interrupt.rs +++ b/core/src/cpu_808x/interrupt.rs @@ -32,7 +32,7 @@ use crate::cpu_808x::*; -impl Cpu { +impl Intel808x { /// Execute the IRET microcode routine. pub fn iret_routine(&mut self) { self.cycle_i(0x0c8); diff --git a/core/src/cpu_808x/jump.rs b/core/src/cpu_808x/jump.rs index 8395f2ed..cc68c28a 100644 --- a/core/src/cpu_808x/jump.rs +++ b/core/src/cpu_808x/jump.rs @@ -34,7 +34,7 @@ use crate::{ util, }; -impl Cpu { +impl Intel808x { /* /// Execute the RELJMP microcode routine, optionally including the jump into the procedure. #[inline] diff --git a/core/src/cpu_808x/logging.rs b/core/src/cpu_808x/logging.rs index f6fc0570..78fb4158 100644 --- a/core/src/cpu_808x/logging.rs +++ b/core/src/cpu_808x/logging.rs @@ -36,6 +36,7 @@ use crate::{ BusStatus, Cpu, DmaState, + Intel808x, QueueOp, Segment, TCycle, @@ -59,7 +60,7 @@ pub enum BusSlotStatus { SlotB(BusStatus, TaCycle), } -impl Cpu { +impl Intel808x { #[inline] pub fn do_cycle_trace(&mut self) { match self.trace_mode { @@ -622,7 +623,7 @@ impl Cpu { token_vec } - pub fn cycle_trace_header(&self) -> Vec { + pub fn cycle_table_header(&self) -> Vec { vec![ "Cycle".to_string(), "icyc".to_string(), diff --git a/core/src/cpu_808x/mod.rs b/core/src/cpu_808x/mod.rs index 8d5cc116..a54adfe3 100644 --- a/core/src/cpu_808x/mod.rs +++ b/core/src/cpu_808x/mod.rs @@ -32,6 +32,8 @@ #![allow(clippy::unusual_byte_groupings)] +pub use crate::cpu_common::Cpu; +use crate::cpu_common::{CpuStringState, CpuSubType, Instruction}; use core::fmt::Display; use lazy_static::lazy_static; use regex::Regex; @@ -43,6 +45,7 @@ mod alu; mod bcd; mod bitwise; mod biu; +mod cpu; mod cycle; mod decode; mod display; @@ -107,7 +110,7 @@ macro_rules! trace_print { }}; } -use crate::cpu_808x::instruction::Instruction; +use crate::cpu_common::{Register16, Register8}; use trace_print; const QUEUE_MAX: usize = 6; @@ -397,36 +400,6 @@ pub enum Register { IP, }*/ -#[derive(Copy, Clone, PartialEq)] -pub enum Register8 { - AL, - CL, - DL, - BL, - AH, - CH, - DH, - BH, -} - -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum Register16 { - AX, - CX, - DX, - BX, - SP, - BP, - SI, - DI, - ES, - CS, - SS, - DS, - PC, - InvalidRegister, -} - #[derive(Copy, Clone)] pub enum OperandType { Immediate8(u8), @@ -609,7 +582,7 @@ impl From for u32 { fn from(cpu_address: CpuAddress) -> Self { match cpu_address { CpuAddress::Flat(a) => a, - CpuAddress::Segmented(s, o) => Cpu::calc_linear_address(s, o), + CpuAddress::Segmented(s, o) => Intel808x::calc_linear_address(s, o), CpuAddress::Offset(a) => a as Self, } } @@ -630,12 +603,12 @@ impl PartialEq for CpuAddress { match (self, other) { (CpuAddress::Flat(a), CpuAddress::Flat(b)) => a == b, (CpuAddress::Flat(a), CpuAddress::Segmented(s, o)) => { - let b = Cpu::calc_linear_address(*s, *o); + let b = Intel808x::calc_linear_address(*s, *o); *a == b } (CpuAddress::Flat(_a), CpuAddress::Offset(_b)) => false, (CpuAddress::Segmented(s, o), CpuAddress::Flat(b)) => { - let a = Cpu::calc_linear_address(*s, *o); + let a = Intel808x::calc_linear_address(*s, *o); a == *b } (CpuAddress::Segmented(s1, o1), CpuAddress::Segmented(s2, o2)) => *s1 == *s2 && *o1 == *o2, @@ -662,9 +635,10 @@ pub struct I8288 { } #[derive(Default)] -pub struct Cpu { +pub struct Intel808x { cpu_type: CpuType, - state: CpuState, + cpu_subtype: CpuSubType, + state: CpuState, a: GeneralRegister, b: GeneralRegister, @@ -892,46 +866,6 @@ pub struct CpuRegisterState { pub flags: u16, } -#[derive(Default, Debug, Clone)] -pub struct CpuStringState { - pub ah: String, - pub al: String, - pub ax: String, - pub bh: String, - pub bl: String, - pub bx: String, - pub ch: String, - pub cl: String, - pub cx: String, - pub dh: String, - pub dl: String, - pub dx: String, - pub sp: String, - pub bp: String, - pub si: String, - pub di: String, - pub cs: String, - pub ds: String, - pub ss: String, - pub es: String, - pub pc: String, - pub ip: String, - pub flags: String, - //odiszapc - pub c_fl: String, - pub p_fl: String, - pub a_fl: String, - pub z_fl: String, - pub s_fl: String, - pub t_fl: String, - pub i_fl: String, - pub d_fl: String, - pub o_fl: String, - pub piq: String, - pub instruction_count: String, - pub cycle_count: String, -} - /* pub enum RegisterType { Register8(u8), @@ -1068,9 +1002,10 @@ pub enum QueueOp { Subsequent, } -impl Cpu { +impl Intel808x { pub fn new( cpu_type: CpuType, + cpu_subtype: CpuSubType, trace_mode: TraceMode, trace_logger: TraceLogger, #[cfg(feature = "cpu_validator")] validator_type: ValidatorType, @@ -1078,17 +1013,20 @@ impl Cpu { #[cfg(feature = "cpu_validator")] validator_mode: ValidatorMode, #[cfg(feature = "cpu_validator")] validator_baud: u32, ) -> Self { - let mut cpu: Cpu = Default::default(); + let mut cpu: Intel808x = Default::default(); - match cpu_type { - CpuType::Harris80C88 | CpuType::Intel8088 => { + match cpu_subtype { + CpuSubType::Harris80C88 | CpuSubType::Intel8088 => { cpu.queue.set_size(4, 1); cpu.fetch_size = TransferSize::Byte; } - CpuType::Intel8086 => { + CpuSubType::Intel8086 => { cpu.queue.set_size(6, 2); cpu.fetch_size = TransferSize::Word; } + _ => { + panic!("Invalid CPU subtype.") + } } #[cfg(feature = "cpu_validator")] @@ -1112,6 +1050,7 @@ impl Cpu { cpu.trace_logger = trace_logger; cpu.trace_mode = trace_mode; cpu.cpu_type = cpu_type; + cpu.cpu_subtype = cpu_subtype; //cpu.instruction_history_on = true; // Control this from config/GUI instead cpu.instruction_history = VecDeque::with_capacity(16); @@ -1121,143 +1060,6 @@ impl Cpu { cpu } - pub fn reset(&mut self) { - log::debug!("CPU Resetting..."); - /* - let trace_logger = std::mem::replace(&mut self.trace_logger, TraceLogger::None); - - // Save non-default values - *self = Self { - // Save parameters to new() - cpu_type: self.cpu_type, - reset_vector: self.reset_vector, - trace_mode: self.trace_mode, - trace_logger, - // Save options - instruction_history_on: self.instruction_history_on, - dram_refresh_simulation: self.dram_refresh_simulation, - halt_resume_delay: self.halt_resume_delay, - off_rails_detection: self.off_rails_detection, - enable_wait_states: self.enable_wait_states, - trace_enabled: self.trace_enabled, - - // Copy bus - bus: self.bus, - - #[cfg(feature = "cpu_validator")] - validator_type: ValidatorType, - #[cfg(feature = "cpu_validator")] - validator_trace: TraceLogger, - ..Self::default() - }; - */ - - self.state = CpuState::Normal; - - self.set_register16(Register16::AX, 0); - self.set_register16(Register16::BX, 0); - self.set_register16(Register16::CX, 0); - self.set_register16(Register16::DX, 0); - self.set_register16(Register16::SP, 0); - self.set_register16(Register16::BP, 0); - self.set_register16(Register16::SI, 0); - self.set_register16(Register16::DI, 0); - self.set_register16(Register16::ES, 0); - - self.set_register16(Register16::SS, 0); - self.set_register16(Register16::DS, 0); - - self.flags = CPU_FLAGS_RESERVED_ON; - - self.queue.flush(); - - if let CpuAddress::Segmented(segment, offset) = self.reset_vector { - self.set_register16(Register16::CS, segment); - self.set_register16(Register16::PC, offset); - } - else { - panic!("Invalid CpuAddress for reset vector."); - } - - self.address_latch = 0; - self.bus_status = BusStatus::Passive; - self.bus_status_latch = BusStatus::Passive; - self.t_cycle = TCycle::Ti; - self.ta_cycle = TaCycle::Td; - self.pl_status = BusStatus::Passive; - self.pl_slot = false; - - self.fetch_state = FetchState::Normal; - - self.instruction_count = 0; - self.int_count = 0; - self.iret_count = 0; - self.instr_cycle = 0; - self.cycle_num = 1; - self.halt_cycles = 0; - self.t_stamp = 0.0; - self.t_step = 0.00000021; - self.t_step_h = 0.000000105; - self.ready = true; - self.in_rep = false; - self.halted = false; - self.reported_halt = false; - self.halt_not_hold = false; - self.opcode0_counter = 0; - self.interrupt_inhibit = false; - self.intr_pending = false; - self.in_int = false; - self.is_error = false; - self.instruction_history.clear(); - self.call_stack.clear(); - self.int_flags = vec![0; 256]; - - self.instruction_reentrant = false; - self.last_ip = 0; - self.last_cs = 0; - self.last_intr = false; - self.jumped = false; - - self.queue_op = QueueOp::Idle; - self.last_queue_op = QueueOp::Idle; - - self.i8288.ale = false; - self.i8288.mrdc = false; - self.i8288.amwc = false; - self.i8288.mwtc = false; - self.i8288.iorc = false; - self.i8288.aiowc = false; - self.i8288.iowc = false; - - self.dram_refresh_tc = false; - self.dram_refresh_retrigger = false; - - self.step_over_target = None; - self.step_over_breakpoint = None; - self.end_addr = 0xFFFFF; - self.stopwatch_running = false; - - self.nx = false; - self.rni = false; - - self.halt_resume_delay = 4; - - // Reset takes 6 cycles before first fetch - self.cycle(); - self.biu_fetch_suspend(); - self.cycles_i(2, &[0x1e4, 0x1e5]); - self.biu_queue_flush(); - self.cycles_i(3, &[0x1e6, 0x1e7, 0x1e8]); - - #[cfg(feature = "cpu_validator")] - { - self.validator_state = CpuValidatorState::Uninitialized; - self.cycle_states.clear(); - } - - trace_print!(self, "Reset CPU! CS: {:04X} IP: {:04X}", self.cs, self.ip()); - } - pub fn get_instruction_ct(&self) -> u64 { self.instruction_count } @@ -1286,20 +1088,8 @@ impl Cpu { } } - /// Return the resolved flat address of CS:CORR(PC) - #[inline] - pub fn flat_ip(&self) -> u32 { - Cpu::calc_linear_address(self.cs, self.ip()) - } - - /// Return the resolved flat address of CS:CORR(PC), adjusted for reentrant instructions - #[inline] - pub fn flat_ip_adjusted(&self) -> u32 { - Cpu::calc_linear_address(self.cs, self.disassembly_ip()) - } - pub fn flat_sp(&self) -> u32 { - Cpu::calc_linear_address(self.ss, self.sp) + Intel808x::calc_linear_address(self.ss, self.sp) } /// Execute the CORR (Correct PC) microcode routine. @@ -1316,14 +1106,6 @@ impl Cpu { self.in_rep } - pub fn bus(&self) -> &BusInterface { - &self.bus - } - - pub fn bus_mut(&mut self) -> &mut BusInterface { - &mut self.bus - } - pub fn get_csip(&self) -> CpuAddress { CpuAddress::Segmented(self.cs, self.ip()) } @@ -1906,7 +1688,7 @@ impl Cpu { self.call_stack.push_back(entry); // Flag the specified CS:IP as a return address - let return_addr = Cpu::calc_linear_address(cs, ip); + let return_addr = Intel808x::calc_linear_address(cs, ip); self.bus.set_flags(return_addr as usize, MEM_RET_BIT); } @@ -1930,9 +1712,9 @@ impl Cpu { let pos = self.call_stack.iter().position(|&call| { return_addr = match call { - CallStackEntry::CallF { ret_cs, ret_ip, .. } => Cpu::calc_linear_address(ret_cs, ret_ip), - CallStackEntry::Call { ret_cs, ret_ip, .. } => Cpu::calc_linear_address(ret_cs, ret_ip), - CallStackEntry::Interrupt { ret_cs, ret_ip, .. } => Cpu::calc_linear_address(ret_cs, ret_ip), + CallStackEntry::CallF { ret_cs, ret_ip, .. } => Intel808x::calc_linear_address(ret_cs, ret_ip), + CallStackEntry::Call { ret_cs, ret_ip, .. } => Intel808x::calc_linear_address(ret_cs, ret_ip), + CallStackEntry::Interrupt { ret_cs, ret_ip, .. } => Intel808x::calc_linear_address(ret_cs, ret_ip), }; return_addr == addr @@ -1943,9 +1725,9 @@ impl Cpu { drained.for_each(|drained_call| { return_addr = match drained_call { - CallStackEntry::CallF { ret_cs, ret_ip, .. } => Cpu::calc_linear_address(ret_cs, ret_ip), - CallStackEntry::Call { ret_cs, ret_ip, .. } => Cpu::calc_linear_address(ret_cs, ret_ip), - CallStackEntry::Interrupt { ret_cs, ret_ip, .. } => Cpu::calc_linear_address(ret_cs, ret_ip), + CallStackEntry::CallF { ret_cs, ret_ip, .. } => Intel808x::calc_linear_address(ret_cs, ret_ip), + CallStackEntry::Call { ret_cs, ret_ip, .. } => Intel808x::calc_linear_address(ret_cs, ret_ip), + CallStackEntry::Interrupt { ret_cs, ret_ip, .. } => Intel808x::calc_linear_address(ret_cs, ret_ip), }; // Clear flags for returns we popped @@ -2323,80 +2105,6 @@ impl Cpu { self.service_events.pop_front() } - pub fn set_option(&mut self, opt: CpuOption) { - match opt { - CpuOption::InstructionHistory(state) => { - log::debug!("Setting InstructionHistory to: {:?}", state); - self.instruction_history.clear(); - self.instruction_history_on = state; - } - CpuOption::ScheduleInterrupt(_state, cycle_target, cycles, retrigger) => { - log::debug!("Setting InterruptHint to: ({},{})", cycle_target, cycles); - self.interrupt_scheduling = true; - self.interrupt_cycle_period = cycle_target; - self.interrupt_cycle_num = cycles; - self.interrupt_retrigger = retrigger; - } - CpuOption::ScheduleDramRefresh(state, cycle_target, cycles, retrigger) => { - log::trace!( - "Setting SimulateDramRefresh to: {:?} ({},{})", - state, - cycle_target, - cycles - ); - self.dram_refresh_simulation = state; - self.dram_refresh_cycle_period = cycle_target; - self.dram_refresh_cycle_num = cycles; - self.dram_refresh_retrigger = retrigger; - self.dram_refresh_tc = false; - } - CpuOption::DramRefreshAdjust(adj) => { - log::debug!("Setting DramRefreshAdjust to: {}", adj); - self.dram_refresh_adjust = adj; - } - CpuOption::HaltResumeDelay(delay) => { - log::debug!("Setting HaltResumeDelay to: {}", delay); - self.halt_resume_delay = delay; - } - CpuOption::OffRailsDetection(state) => { - log::debug!("Setting OffRailsDetection to: {:?}", state); - self.off_rails_detection = state; - } - CpuOption::EnableWaitStates(state) => { - log::debug!("Setting EnableWaitStates to: {:?}", state); - self.enable_wait_states = state; - } - CpuOption::TraceLoggingEnabled(state) => { - log::debug!("Setting TraceLoggingEnabled to: {:?}", state); - self.trace_enabled = state; - - // Flush the trace log file on stopping trace so that we can immediately - // see results otherwise buffered - if state == false { - self.trace_flush(); - } - } - CpuOption::EnableServiceInterrupt(state) => { - log::debug!("Setting EnableServiceInterrupt to: {:?}", state); - self.enable_service_interrupt = state; - } - } - } - - pub fn get_option(&mut self, opt: CpuOption) -> bool { - match opt { - CpuOption::InstructionHistory(_) => self.instruction_history_on, - CpuOption::ScheduleInterrupt(..) => self.interrupt_cycle_period > 0, - CpuOption::ScheduleDramRefresh(..) => self.dram_refresh_simulation, - CpuOption::DramRefreshAdjust(..) => true, - CpuOption::HaltResumeDelay(..) => true, - CpuOption::OffRailsDetection(_) => self.off_rails_detection, - CpuOption::EnableWaitStates(_) => self.enable_wait_states, - CpuOption::TraceLoggingEnabled(_) => self.trace_enabled, - CpuOption::EnableServiceInterrupt(_) => self.enable_service_interrupt, - } - } - pub fn get_cycle_trace(&self) -> &Vec { &self.trace_str_vec } diff --git a/core/src/cpu_808x/muldiv.rs b/core/src/cpu_808x/muldiv.rs index d8deb4c4..db3c3cf0 100644 --- a/core/src/cpu_808x/muldiv.rs +++ b/core/src/cpu_808x/muldiv.rs @@ -33,7 +33,7 @@ use crate::{cpu_808x::*, cpu_common::alu::*}; pub trait Cord: Sized { - fn cord(self, cpu: &mut Cpu, a: u16, b: u16, c: u16) -> Result<(u16, u16, bool), bool>; + fn cord(self, cpu: &mut Intel808x, a: u16, b: u16, c: u16) -> Result<(u16, u16, bool), bool>; } macro_rules! impl_cord { @@ -41,7 +41,7 @@ macro_rules! impl_cord { impl Cord for $prim { /// Implementation of the 8088 microcode CORD division co-routine. /// Implemented for either 8 bit or 16 bit operand. - fn cord(self, cpu: &mut Cpu, a: u16, b: u16, c: u16) -> Result<(u16, u16, bool), bool> { + fn cord(self, cpu: &mut Intel808x, a: u16, b: u16, c: u16) -> Result<(u16, u16, bool), bool> { let mut internal_counter; let mut tmpa: u16 = a; @@ -170,7 +170,7 @@ impl_cord!(u8); impl_cord!(u16); pub trait Corx: Sized { - fn corx(self, cpu: &mut Cpu, b: u16, c: u16, carry: bool) -> (u16, u16); + fn corx(self, cpu: &mut Intel808x, b: u16, c: u16, carry: bool) -> (u16, u16); } macro_rules! impl_corx { @@ -179,7 +179,7 @@ macro_rules! impl_corx { /// Implementation of the 8088 microcode CORX multiplication co-routine. /// Implemented for either 8 bit or 16 bit operands. /// tmpa is used to select the size of the operation, but the value is not used. - fn corx(self, cpu: &mut Cpu, b: u16, c: u16, mut carry: bool) -> (u16, u16) { + fn corx(self, cpu: &mut Intel808x, b: u16, c: u16, mut carry: bool) -> (u16, u16) { let mut internal_counter; let mut tmpa: u16; let tmpb: u16 = b; @@ -239,7 +239,8 @@ impl_corx!(u8); impl_corx!(u16); pub trait CorNegate: Sized { - fn cor_negate(self, cpu: &mut Cpu, b: u16, c: u16, neg_flag: bool, skip: bool) -> (u16, u16, u16, bool, bool); + fn cor_negate(self, cpu: &mut Intel808x, b: u16, c: u16, neg_flag: bool, skip: bool) + -> (u16, u16, u16, bool, bool); } macro_rules! impl_cor_negate { @@ -252,7 +253,7 @@ macro_rules! impl_cor_negate { /// Returns tmpa, tmpb, tmpc, carry and negate flag. fn cor_negate( self, - cpu: &mut Cpu, + cpu: &mut Intel808x, mut tmpb: u16, mut tmpc: u16, mut neg_flag: bool, @@ -325,7 +326,7 @@ macro_rules! impl_cor_negate { impl_cor_negate!(u8); impl_cor_negate!(u16); -impl Cpu { +impl Intel808x { #[allow(dead_code)] #[allow(unused_assignments)] // This isn't pretty but we are trying to mirror the microcode /// Microcode routine for multiplication, 8 bit diff --git a/core/src/cpu_808x/stack.rs b/core/src/cpu_808x/stack.rs index e005ad50..63092ab9 100644 --- a/core/src/cpu_808x/stack.rs +++ b/core/src/cpu_808x/stack.rs @@ -32,7 +32,7 @@ use crate::cpu_808x::{biu::*, *}; -impl Cpu { +impl Intel808x { pub fn push_u8(&mut self, data: u8, flag: ReadWriteFlag) { // Stack pointer grows downwards self.sp = self.sp.wrapping_sub(2); @@ -67,25 +67,25 @@ impl Cpu { Register16::SI => self.si, Register16::DI => self.di, Register16::CS => { - if let CpuType::Harris80C88 = self.cpu_type { + if let CpuSubType::Harris80C88 = self.cpu_subtype { self.interrupt_inhibit = true; } self.cs } Register16::DS => { - if let CpuType::Harris80C88 = self.cpu_type { + if let CpuSubType::Harris80C88 = self.cpu_subtype { self.interrupt_inhibit = true; } self.ds } Register16::SS => { - if let CpuType::Harris80C88 = self.cpu_type { + if let CpuSubType::Harris80C88 = self.cpu_subtype { self.interrupt_inhibit = true; } self.ss } Register16::ES => { - if let CpuType::Harris80C88 = self.cpu_type { + if let CpuSubType::Harris80C88 = self.cpu_subtype { self.interrupt_inhibit = true; } self.es @@ -115,13 +115,13 @@ impl Cpu { Register16::DI => self.di = data, Register16::CS => { self.cs = data; - if let CpuType::Harris80C88 = self.cpu_type { + if let CpuSubType::Harris80C88 = self.cpu_subtype { self.interrupt_inhibit = true; } } Register16::DS => { self.ds = data; - if let CpuType::Harris80C88 = self.cpu_type { + if let CpuSubType::Harris80C88 = self.cpu_subtype { self.interrupt_inhibit = true; } } @@ -131,7 +131,7 @@ impl Cpu { } Register16::ES => { self.es = data; - if let CpuType::Harris80C88 = self.cpu_type { + if let CpuSubType::Harris80C88 = self.cpu_subtype { self.interrupt_inhibit = true; } } diff --git a/core/src/cpu_808x/step.rs b/core/src/cpu_808x/step.rs index 5813c486..f2e2a12e 100644 --- a/core/src/cpu_808x/step.rs +++ b/core/src/cpu_808x/step.rs @@ -32,7 +32,7 @@ use crate::cpu_808x::*; -impl Cpu { +impl Intel808x { /// Run a single instruction. /// /// We divide instruction execution into separate fetch/decode and microcode execution phases. @@ -75,7 +75,7 @@ impl Cpu { // Sometimes it is more convenient for us to think of the current ip address, which can be calculated on the // fly from PC by the ip() instruction, but only usefully on instruction boundaries, such as now. self.instruction_ip = self.ip(); - self.instruction_address = Cpu::calc_linear_address(self.cs, self.instruction_ip); + self.instruction_address = Intel808x::calc_linear_address(self.cs, self.instruction_ip); instruction_address = self.instruction_address; //log::warn!("instruction address: {:05X}", instruction_address); @@ -145,7 +145,7 @@ impl Cpu { // anyway. if self.trace_mode == TraceMode::CycleText { self.bus.seek(instruction_address as usize); - self.i = match Cpu::decode(&mut self.bus, true) { + self.i = match Intel808x::decode(&mut self.bus, true) { Ok(i) => i, Err(_) => { self.is_running = false; @@ -160,7 +160,7 @@ impl Cpu { // Fetch and decode the current instruction. This uses the CPU's own ByteQueue trait // implementation, which fetches instruction bytes through the processor instruction queue. //log::warn!("decoding instruction..."); - self.i = match Cpu::decode(self, true) { + self.i = match Intel808x::decode(self, true) { Ok(i) => i, Err(_) => { self.is_running = false; @@ -486,7 +486,7 @@ impl Cpu { #[cfg(feature = "cpu_validator")] pub fn validate_begin(&mut self, instruction_address: u32) { - let v_address = Cpu::calc_linear_address(self.vregs.cs, self.vregs.ip); + let v_address = Intel808x::calc_linear_address(self.vregs.cs, self.vregs.ip); if v_address != instruction_address { log::warn!( "Validator address mismatch: {:05X} != {:05X}", diff --git a/core/src/cpu_808x/string.rs b/core/src/cpu_808x/string.rs index 66d3cceb..ed67cc48 100644 --- a/core/src/cpu_808x/string.rs +++ b/core/src/cpu_808x/string.rs @@ -32,7 +32,7 @@ use crate::{cpu_808x::*, cpu_common::alu::AluSub}; -impl Cpu { +impl Intel808x { pub fn string_op(&mut self, opcode: Mnemonic, segment_override: Option) { let segment_base_ds = segment_override.unwrap_or(Segment::DS); diff --git a/core/src/cpu_common/builder.rs b/core/src/cpu_common/builder.rs new file mode 100644 index 00000000..0936cca1 --- /dev/null +++ b/core/src/cpu_common/builder.rs @@ -0,0 +1,136 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_common::builder.rs + + Implements common functionality shared by different CPU types. + +*/ +use crate::{ + cpu_808x::Intel808x, + cpu_common::{CpuDispatch, CpuSubType, CpuType, TraceMode}, + cpu_validator::{ValidatorMode, ValidatorType}, + tracelogger::TraceLogger, +}; +use anyhow::{bail, Result}; + +#[derive(Default)] +pub struct CpuBuilder { + cpu_type: Option, + cpu_subtype: Option, + trace_mode: TraceMode, + trace_logger: Option, + #[cfg(feature = "cpu_validator")] + validator_type: ValidatorType, + #[cfg(feature = "cpu_validator")] + validator_mode: Option, + #[cfg(feature = "cpu_validator")] + validator_logger: Option, + #[cfg(feature = "cpu_validator")] + validator_baud: Option, +} + +impl CpuBuilder { + pub fn new() -> CpuBuilder { + CpuBuilder { ..Default::default() } + } + + pub fn build(&mut self) -> Result { + // Build the CPU + + if let Some(cpu_type) = self.cpu_type { + match cpu_type { + CpuType::Intel808x => { + let mut cpu = Intel808x::new( + CpuType::Intel808x, + CpuSubType::Intel8088, + self.trace_mode, + self.trace_logger.take().unwrap_or_default(), + #[cfg(feature = "cpu_validator")] + self.validator_type, + #[cfg(feature = "cpu_validator")] + self.validator_logger.take().unwrap_or_default(), + #[cfg(feature = "cpu_validator")] + self.validator_mode.take().unwrap_or_default(), + #[cfg(feature = "cpu_validator")] + self.validator_baud.take().unwrap_or_default(), + ); + return Ok(cpu.into()); + } + _ => { + bail!("Unimplemented CPU type: {:?}", cpu_type); + } + } + } + else { + bail!("CpuType is required."); + } + } + + pub fn with_cpu_type(mut self, cpu_type: CpuType) -> Self { + self.cpu_type = Some(cpu_type); + self + } + + pub fn with_cpu_subtype(mut self, cpu_subtype: CpuSubType) -> Self { + self.cpu_subtype = Some(cpu_subtype); + self + } + + pub fn with_trace_mode(mut self, trace_mode: TraceMode) -> Self { + self.trace_mode = trace_mode; + self + } + + pub fn with_trace_logger(mut self, trace_logger: TraceLogger) -> Self { + self.trace_logger = Some(trace_logger); + self + } + + #[cfg(feature = "cpu_validator")] + pub fn with_validator_type(mut self, validator_type: ValidatorType) -> Self { + self.validator_type = validator_type; + self + } + + #[cfg(feature = "cpu_validator")] + pub fn with_validator_mode(mut self, validator_mode: ValidatorMode) -> Self { + self.validator_mode = Some(validator_mode); + self + } + + #[cfg(feature = "cpu_validator")] + pub fn with_validator_logger(mut self, validator_logger: TraceLogger) -> Self { + self.validator_logger = Some(validator_logger); + self + } + + #[cfg(feature = "cpu_validator")] + pub fn with_validator_baud(mut self, validator_baud: u32) -> Self { + self.validator_baud = Some(validator_baud); + self + } +} diff --git a/core/src/cpu_common/mod.rs b/core/src/cpu_common/mod.rs index 834bb175..272bebce 100644 --- a/core/src/cpu_common/mod.rs +++ b/core/src/cpu_common/mod.rs @@ -32,19 +32,125 @@ #![allow(dead_code)] +pub mod alu; +pub mod builder; + +use crate::{ + breakpoints::{BreakPointType, StopWatchData}, + bus::{BusInterface, ClockFactor}, + bytequeue::ByteQueue, + cpu_808x::{ + mnemonic::Mnemonic, + CpuAddress, + CpuError, + Intel808x, + OperandSize, + OperandType, + Segment, + ServiceEvent, + StepResult, + }, + cpu_validator::{CycleState, VRegisters}, + syntax_token::SyntaxToken, +}; +use enum_dispatch::enum_dispatch; use serde::Deserialize; use std::str::FromStr; -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, PartialEq)] +pub enum Register8 { + AL, + CL, + DL, + BL, + AH, + CH, + DH, + BH, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Register16 { + AX, + CX, + DX, + BX, + SP, + BP, + SI, + DI, + ES, + CS, + SS, + DS, + PC, + InvalidRegister, +} + +#[derive(Default, Debug, Clone)] +pub struct CpuStringState { + pub ah: String, + pub al: String, + pub ax: String, + pub bh: String, + pub bl: String, + pub bx: String, + pub ch: String, + pub cl: String, + pub cx: String, + pub dh: String, + pub dl: String, + pub dx: String, + pub sp: String, + pub bp: String, + pub si: String, + pub di: String, + pub cs: String, + pub ds: String, + pub ss: String, + pub es: String, + pub pc: String, + pub ip: String, + pub flags: String, + //odiszapc + pub c_fl: String, + pub p_fl: String, + pub a_fl: String, + pub z_fl: String, + pub s_fl: String, + pub t_fl: String, + pub i_fl: String, + pub d_fl: String, + pub o_fl: String, + pub piq: String, + pub instruction_count: String, + pub cycle_count: String, +} + +#[derive(Copy, Clone, Debug, Deserialize, PartialEq)] pub enum CpuType { - Intel8088, - Intel8086, - Harris80C88, + Intel808x, +} + +impl CpuType { + pub fn decode(&self, bytes: &mut impl ByteQueue, peek: bool) -> Result> { + match self { + CpuType::Intel808x => Intel808x::decode(bytes, peek), + } + } + pub fn tokenize_instruction(&self, instruction: &Instruction) -> Vec { + match self { + CpuType::Intel808x => Intel808x::tokenize_instruction(instruction), + } + } } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Default, Deserialize, PartialEq)] pub enum CpuSubType { + #[default] + None, Intel8088, + Intel8086, Harris80C88, } @@ -87,7 +193,7 @@ impl Default for TraceMode { impl Default for CpuType { fn default() -> Self { - CpuType::Intel8088 + CpuType::Intel808x } } @@ -104,12 +210,104 @@ pub enum CpuOption { EnableServiceInterrupt(bool), } -use crate::cpu_808x::*; +pub fn calc_linear_address(segment: u16, offset: u16) -> u32 { + (((segment as u32) << 4) + offset as u32) & 0xFFFFFu32 +} -pub mod alu; +#[derive(Clone)] +pub struct Instruction { + pub decode_idx: usize, + pub opcode: u8, + pub prefixes: u32, + pub address: u32, + pub size: u32, + pub mnemonic: Mnemonic, + pub segment_override: Option, + pub operand1_type: OperandType, + pub operand1_size: OperandSize, + pub operand2_type: OperandType, + pub operand2_size: OperandSize, +} -impl Cpu { - pub fn common_test(&self) { - //log::trace!("I'm a common cpu function!"); +impl Default for Instruction { + fn default() -> Self { + Self { + decode_idx: 0, + opcode: 0, + prefixes: 0, + address: 0, + size: 1, + mnemonic: Mnemonic::NOP, + segment_override: None, + operand1_type: OperandType::NoOperand, + operand1_size: OperandSize::NoOperand, + operand2_type: OperandType::NoOperand, + operand2_size: OperandSize::NoOperand, + } } } + +#[enum_dispatch] +pub enum CpuDispatch { + Intel808x, +} + +#[enum_dispatch(CpuDispatch)] +pub trait Cpu { + // General CPU control + fn reset(&mut self); + fn set_reset_vector(&mut self, address: CpuAddress); + fn set_end_address(&mut self, address: CpuAddress); + fn set_nmi(&mut self, state: bool); + fn set_intr(&mut self, state: bool); + fn step(&mut self, skip_breakpoint: bool) -> Result<(StepResult, u32), CpuError>; + fn step_finish(&mut self) -> Result; + + fn in_rep(&self) -> bool; + fn get_type(&self) -> CpuType; + + fn get_ip(&mut self) -> u16; + fn get_register16(&self, reg: Register16) -> u16; + fn set_register16(&mut self, reg: Register16, value: u16); + fn get_flags(&self) -> u16; + fn set_flags(&mut self, flags: u16); + fn get_cycle_ct(&self) -> (u64, u64); + fn get_instruction_ct(&self) -> u64; + fn flat_ip(&self) -> u32; + fn flat_ip_disassembly(&self) -> u32; + fn flat_sp(&self) -> u32; + fn dump_instruction_history_string(&self) -> String; + fn dump_instruction_history_tokens(&self) -> Vec>; + fn dump_call_stack(&self) -> String; + fn get_service_event(&mut self) -> Option; + fn get_cycle_states(&self) -> &Vec; + fn get_cycle_trace(&self) -> &Vec; + fn get_cycle_trace_tokens(&self) -> &Vec>; + #[cfg(feature = "cpu_validator")] + fn get_vregisters(&self) -> VRegisters; + fn get_string_state(&self) -> CpuStringState; + + // Eval + fn eval_address(&self, expr: &str) -> Option; + + // Breakpoints + fn clear_breakpoint_flag(&mut self); + fn set_breakpoints(&mut self, bp_list: Vec); + fn get_step_over_breakpoint(&self) -> Option; + fn set_step_over_breakpoint(&mut self, address: CpuAddress); + fn get_sw_data(&self) -> Vec; + fn set_stopwatch(&mut self, sw_idx: usize, start: u32, stop: u32); + + // CPU options + fn set_option(&mut self, opt: CpuOption); + fn get_option(&self, opt: CpuOption) -> bool; + + // Bus methods + fn bus(&self) -> &BusInterface; + fn bus_mut(&mut self) -> &mut BusInterface; + + // Logging methods + fn cycle_table_header(&self) -> Vec; + fn emit_header(&mut self); + fn trace_flush(&mut self); +} diff --git a/core/src/cpu_validator.rs b/core/src/cpu_validator.rs index 3ffc7fec..76c9b057 100644 --- a/core/src/cpu_validator.rs +++ b/core/src/cpu_validator.rs @@ -81,8 +81,9 @@ impl FromStr for ValidatorType { } } -#[derive(PartialEq, Debug, Copy, Clone)] +#[derive(PartialEq, Debug, Copy, Clone, Default)] pub enum ValidatorMode { + #[default] Instruction, Cycle, } diff --git a/core/src/machine.rs b/core/src/machine.rs index f1b315f2..7f956005 100644 --- a/core/src/machine.rs +++ b/core/src/machine.rs @@ -33,6 +33,7 @@ the appropriate methods on Bus. */ +use crate::cpu_common::{CpuDispatch, CpuSubType}; use log; use anyhow::{anyhow, Error}; @@ -48,8 +49,8 @@ use crate::{ breakpoints::BreakPointType, bus::{BusInterface, ClockFactor, DeviceEvent, MEM_CP_BIT}, coreconfig::CoreConfig, - cpu_808x::{Cpu, CpuAddress, CpuError, ServiceEvent, StepResult}, - cpu_common::{CpuOption, CpuType, TraceMode}, + cpu_808x::{Intel808x, CpuAddress, CpuError, ServiceEvent, StepResult}, + cpu_common::{Cpu, CpuOption, CpuType, TraceMode}, device_traits::videocard::{VideoCard, VideoCardId, VideoCardInterface, VideoCardState, VideoOption}, devices::{ dma::DMAControllerStringState, @@ -69,6 +70,8 @@ use crate::{ }; use ringbuf::{Consumer, Producer, RingBuffer}; +use crate::cpu_common::builder::CpuBuilder; +use crate::cpu_validator::ValidatorMode; use crate::devices::ppi::PpiDisplayState; use crate::machine_types::OnHaltBehavior; @@ -428,7 +431,7 @@ pub struct Machine { sound_player: Option, rom_manifest: MachineRomManifest, load_bios: bool, - cpu: Cpu, + cpu: CpuDispatch, speaker_buf_producer: Producer, pit_data: PitData, debug_snd_file: Option, @@ -490,20 +493,42 @@ impl Machine { #[cfg(feature = "cpu_validator")] use crate::cpu_validator::ValidatorMode; - //noinspection ALL - let mut cpu = Cpu::new( - CpuType::Intel8088, - trace_mode, - trace_logger, - #[cfg(feature = "cpu_validator")] - core_config.get_validator_type().unwrap_or_default(), - #[cfg(feature = "cpu_validator")] - validator_trace, - #[cfg(feature = "cpu_validator")] - ValidatorMode::Cycle, - #[cfg(feature = "cpu_validator")] - core_config.get_validator_baud().unwrap_or(1_000_000), - ); + // Build the CPU + let mut cpu; + + #[cfg(feature = "cpu_validator")] + { + cpu = match CpuBuilder::new() + .with_cpu_type(CpuType::Intel808x) + .with_cpu_subtype(CpuSubType::Intel8088) + .with_trace_mode(trace_mode) + .with_trace_logger(trace_logger) + .with_validator_type(core_config.get_validator_type().unwrap_or_default()) + .with_validator_mode(ValidatorMode::Cycle) + .with_validator_logger(validator_trace) + .with_validator_baud(core_config.get_validator_baud().unwrap_or(1_000_000)) + .build() { + Ok(cpu) => cpu, + Err(e) => { + log::error!("Failed to build CPU: {}", e); + std::process::exit(1); + } + } + }; + #[cfg(not(feature = "cpu_validator"))] + { + cpu = match CpuBuilder::new() + .with_cpu_type(CpuType::Intel808x) + .with_cpu_subtype(CpuSubType::Intel8088) + .with_trace_mode(trace_mode) + .build() { + Ok(cpu) => cpu, + Err(e) => { + log::error!("Failed to build CPU: {}", e); + std::process::exit(1); + } + } + }; cpu.set_option(CpuOption::TraceLoggingEnabled(core_config.get_cpu_trace_on())); @@ -716,7 +741,7 @@ impl Machine { } pub fn load_program(&mut self, program: &[u8], program_seg: u16, program_ofs: u16) -> Result<(), bool> { - let location = Cpu::calc_linear_address(program_seg, program_ofs); + let location = Intel808x::calc_linear_address(program_seg, program_ofs); self.cpu.bus_mut().copy_from(program, location as usize, 0, false)?; @@ -724,8 +749,7 @@ impl Machine { .set_reset_vector(CpuAddress::Segmented(program_seg, program_ofs)); self.cpu.reset(); - self.cpu - .set_end_address(((location as usize) + program.len()) & 0xFFFFF); + //self.cpu.set_end_address(((location as usize) + program.len()) & 0xFFFFF); Ok(()) } @@ -771,7 +795,7 @@ impl Machine { */ - pub fn cpu(&self) -> &Cpu { + pub fn cpu(&self) -> &CpuDispatch { &self.cpu } @@ -994,7 +1018,7 @@ impl Machine { pub fn set_stopwatch(&mut self, sw_idx: usize, start: u32, stop: u32) { self.cpu.set_stopwatch(sw_idx, start, stop) - } + } pub fn reset(&mut self) { // TODO: Reload any program specified here? @@ -1190,7 +1214,7 @@ impl Machine { // break; // } - let flat_address = self.cpu.flat_ip_adjusted(); + let flat_address = self.cpu.flat_ip_disassembly(); // Match checkpoints. The first check is against a simple bit flag so that we do not // need to constantly do a hash lookup. diff --git a/core/src/machine_config.rs b/core/src/machine_config.rs index 95e90deb..cbaa4473 100644 --- a/core/src/machine_config.rs +++ b/core/src/machine_config.rs @@ -181,7 +181,7 @@ impl Default for MachineDescriptor { timer_crystal: None, bus_crystal: IBM_PC_SYSTEM_CLOCK, open_bus_byte: 0xFF, - cpu_type: CpuType::Intel8088, + cpu_type: CpuType::Intel808x, cpu_factor: ClockFactor::Divisor(3), cpu_turbo_factor: ClockFactor::Divisor(2), bus_type: BusType::Isa8, @@ -233,7 +233,7 @@ lazy_static! { system_crystal: IBM_PC_SYSTEM_CLOCK, timer_crystal: None, bus_crystal: IBM_PC_SYSTEM_CLOCK, - cpu_type: CpuType::Intel8088, + cpu_type: CpuType::Intel808x, cpu_factor: ClockFactor::Divisor(3), cpu_turbo_factor: ClockFactor::Divisor(2), bus_type: BusType::Isa8, @@ -256,7 +256,7 @@ lazy_static! { timer_crystal: None, bus_crystal: IBM_PC_SYSTEM_CLOCK, open_bus_byte: 0xE8, - cpu_type: CpuType::Intel8088, + cpu_type: CpuType::Intel808x, cpu_factor: ClockFactor::Divisor(3), cpu_turbo_factor: ClockFactor::Divisor(2), bus_type: BusType::Isa8, diff --git a/frontends/martypc_desktop_wgpu/src/cpu_test/common.rs b/frontends/martypc_desktop_wgpu/src/cpu_test/common.rs index a5339447..95ca5735 100644 --- a/frontends/martypc_desktop_wgpu/src/cpu_test/common.rs +++ b/frontends/martypc_desktop_wgpu/src/cpu_test/common.rs @@ -43,7 +43,10 @@ use std::{ }; use flate2::read::GzDecoder; -use marty_core::cpu_validator::{BusCycle, BusState, CycleState, VRegisters}; +use marty_core::{ + cpu_common::CpuDispatch, + cpu_validator::{BusCycle, BusState, CycleState, VRegisters}, +}; use serde_derive::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] @@ -461,7 +464,7 @@ pub fn validate_cycles( } pub fn validate_memory( - cpu: &Cpu, + cpu: &CpuDispatch, final_ram: &Vec<[u32; 2]>, flags_on_stack: bool, log: &mut BufWriter, diff --git a/frontends/martypc_desktop_wgpu/src/cpu_test/run_tests.rs b/frontends/martypc_desktop_wgpu/src/cpu_test/run_tests.rs index e3b4bb5c..df100f33 100644 --- a/frontends/martypc_desktop_wgpu/src/cpu_test/run_tests.rs +++ b/frontends/martypc_desktop_wgpu/src/cpu_test/run_tests.rs @@ -55,6 +55,7 @@ use config_toml_bpaf::{ConfigFileParams, TestMode}; use marty_core::{ bytequeue::ByteQueue, cpu_808x::{mnemonic::Mnemonic, Cpu, *}, + cpu_common, cpu_common::{CpuOption, CpuType}, cpu_validator::ValidatorType, tracelogger::TraceLogger, @@ -69,6 +70,10 @@ use crate::{ use crate::cpu_test::common::{TestFileLoad, TestResultSummary}; use colored::*; use flate2::read::GzDecoder; +use marty_core::{ + cpu_common::{builder::CpuBuilder, CpuSubType, Register16}, + cpu_validator::ValidatorMode, +}; pub fn run_runtests(config: ConfigFileParams) { let mut test_path = "./tests".to_string(); @@ -297,11 +302,11 @@ fn run_tests( log: &mut BufWriter, ) -> TestResult { // Create the cpu trace file, if specified - let mut cpu_trace_log = TraceLogger::None; + let mut trace_logger = TraceLogger::None; if let Some(trace_filename) = &config.machine.cpu.trace_file { log::warn!("Using CPU trace log: {:?}", trace_filename); - cpu_trace_log = TraceLogger::from_filename(&trace_filename); + trace_logger = TraceLogger::from_filename(&trace_filename); } // Create the validator trace file, if specified @@ -316,19 +321,31 @@ fn run_tests( #[cfg(feature = "cpu_validator")] use marty_core::cpu_validator::ValidatorMode; - let mut cpu = Cpu::new( - CpuType::Intel8088, - trace_mode, - cpu_trace_log, - #[cfg(feature = "cpu_validator")] - ValidatorType::None, - #[cfg(feature = "cpu_validator")] - validator_trace, - #[cfg(feature = "cpu_validator")] - ValidatorMode::Instruction, - #[cfg(feature = "cpu_validator")] - config.validator.baud_rate.unwrap_or(1_000_000), - ); + let mut cpu; + #[cfg(feature = "cpu_validator")] + { + cpu = match CpuBuilder::new() + .with_cpu_type(CpuType::Intel808x) + .with_cpu_subtype(CpuSubType::Intel8088) + .with_trace_mode(trace_mode) + .with_trace_logger(trace_logger) + .with_validator_type(ValidatorType::None) + .with_validator_mode(ValidatorMode::Instruction) + .with_validator_logger(validator_trace) + .with_validator_baud(config.validator.baud_rate.unwrap_or(1_000_000)) + .build() + { + Ok(cpu) => cpu, + Err(e) => { + log::error!("Failed to build CPU: {}", e); + std::process::exit(1); + } + } + }; + #[cfg(not(feature = "cpu_validator"))] + { + panic!("Validator feature not enabled!") + }; if config.machine.cpu.trace_on { cpu.set_option(CpuOption::TraceLoggingEnabled(true)); @@ -398,11 +415,11 @@ fn run_tests( } // Decode this instruction - let instruction_address = Cpu::calc_linear_address(cpu.get_register16(Register16::CS), cpu.ip()); + let instruction_address = cpu_common::calc_linear_address(cpu.get_register16(Register16::CS), cpu.get_ip()); cpu.bus_mut().seek(instruction_address as usize); - let mut i = match Cpu::decode(cpu.bus_mut(), true) { + let mut i = match cpu.get_type().decode(cpu.bus_mut(), true) { Ok(i) => i, Err(_) => { _ = writeln!(log, "Instruction decode error!"); @@ -451,11 +468,14 @@ fn run_tests( );*/ // Set terminating address for CPU validator. - let end_address = - Cpu::calc_linear_address(cpu.get_register16(Register16::CS), cpu.ip().wrapping_add(i.size as u16)); + let end_address = cpu_common::calc_linear_address( + cpu.get_register16(Register16::CS), + cpu.get_ip().wrapping_add(i.size as u16), + ); //log::debug!("Setting end address: {:05X}", end_address); - cpu.set_end_address(end_address as usize); + + cpu.set_end_address(CpuAddress::Flat(end_address)); let mut flags_on_stack = false; let mut debug_mnemonic = false; @@ -472,7 +492,8 @@ fn run_tests( | Mnemonic::SCASB | Mnemonic::SCASW => { // limit cx to 31 - cpu.set_register16(Register16::CX, cpu.get_register16(Register16::CX) & 0x7F); + let cx = cpu.get_register16(Register16::CX); + cpu.set_register16(Register16::CX, cx & 0x7F); rep = true; } Mnemonic::DIV | Mnemonic::IDIV => { @@ -554,10 +575,10 @@ fn run_tests( trace_error!(log, "{}| Test {:05}: Register validation failed", opcode_string, n); trace_error!(log, "Test specified:"); trace_error!(log, "{}", test.final_state.regs); - trace_error!(log, "{}", Cpu::flags_string(test.final_state.regs.flags)); + trace_error!(log, "{}", Intel808x::flags_string(test.final_state.regs.flags)); trace_error!(log, "CPU reported:"); trace_error!(log, "{}", vregs); - trace_error!(log, "{}", Cpu::flags_string(cpu.get_flags())); + trace_error!(log, "{}", Intel808x::flags_string(cpu.get_flags())); if test.cycles.len() != cpu_cycles.len() { _ = writeln!( @@ -737,6 +758,5 @@ fn run_tests( } results.duration = test_start.elapsed(); - results } diff --git a/frontends/martypc_desktop_wgpu/src/emulator/mod.rs b/frontends/martypc_desktop_wgpu/src/emulator/mod.rs index 8d739630..4ef189ae 100644 --- a/frontends/martypc_desktop_wgpu/src/emulator/mod.rs +++ b/frontends/martypc_desktop_wgpu/src/emulator/mod.rs @@ -45,7 +45,7 @@ use frontend_common::{ vhd_manager::VhdManager, }; use marty_core::{ - cpu_common::CpuOption, + cpu_common::{Cpu, CpuOption}, machine::{ExecutionControl, Machine, MachineEvent, MachineState}, vhd::VirtualHardDisk, }; @@ -208,7 +208,7 @@ impl Emulator { .set_mode(self.config.machine.cpu.trace_mode.unwrap_or_default()); self.gui .cycle_trace_viewer - .set_header(self.machine.cpu().cycle_trace_header()); + .set_header(self.machine.cpu().cycle_table_header()); // Debug mode on? if self.config.emulator.debug_mode { diff --git a/frontends/martypc_desktop_wgpu/src/event_loop/egui_events.rs b/frontends/martypc_desktop_wgpu/src/event_loop/egui_events.rs index 99ff64b6..8256b4f9 100644 --- a/frontends/martypc_desktop_wgpu/src/event_loop/egui_events.rs +++ b/frontends/martypc_desktop_wgpu/src/event_loop/egui_events.rs @@ -33,7 +33,8 @@ use crate::Emulator; use display_manager_wgpu::DisplayManager; use marty_core::{ breakpoints::BreakPointType, - cpu_common::CpuOption, + cpu_common, + cpu_common::{Cpu, CpuOption}, device_traits::videocard::ClockingMode, machine::MachineState, vhd, @@ -50,7 +51,7 @@ use marty_egui::{ use std::{mem::discriminant, time::Duration}; use frontend_common::constants::{LONG_NOTIFICATION_TIME, NORMAL_NOTIFICATION_TIME, SHORT_NOTIFICATION_TIME}; -use marty_core::vhd::VirtualHardDisk; +use marty_core::{cpu_common::Register16, vhd::VirtualHardDisk}; use videocard_renderer::AspectCorrectionMode; use winit::event_loop::EventLoopWindowTarget; @@ -419,10 +420,15 @@ pub fn handle_egui_event(emu: &mut Emulator, elwt: &EventLoopWindowTarget<()>, g // } } GuiEvent::DumpCS => { + let cs = emu.machine.cpu().get_register16(Register16::CS); + let flat_cs = cpu_common::calc_linear_address(cs, 0); + log::info!("Dumping CS: {:04X} ({:08X})", cs, flat_cs); + + let end = flat_cs + 0x10000; emu.rm .get_available_filename("dump", "cs_dump", Some("bin")) .ok() - .map(|path| emu.machine.cpu().dump_cs(&path)) + .map(|path| emu.machine.bus().dump_mem_range(flat_cs, end, &path)) .or_else(|| { log::error!("Failed to get available filename for memory dump!"); None @@ -507,7 +513,8 @@ pub fn handle_egui_event(emu: &mut Emulator, elwt: &EventLoopWindowTarget<()>, g } GuiEvent::TokenHover(addr) => { // Hovered over a token in a TokenListView. - let debug = emu.machine.bus_mut().get_memory_debug(*addr); + let cpu_type = emu.machine.cpu().get_type(); + let debug = emu.machine.bus_mut().get_memory_debug(cpu_type, *addr); emu.gui.memory_viewer.set_hover_text(format!("{}", debug)); } GuiEvent::FlushLogs => { diff --git a/frontends/martypc_desktop_wgpu/src/event_loop/egui_update.rs b/frontends/martypc_desktop_wgpu/src/event_loop/egui_update.rs index 0b077415..c4043d8d 100644 --- a/frontends/martypc_desktop_wgpu/src/event_loop/egui_update.rs +++ b/frontends/martypc_desktop_wgpu/src/event_loop/egui_update.rs @@ -34,6 +34,7 @@ use display_manager_wgpu::DisplayManager; use marty_core::{ bytequeue::ByteQueue, cpu_808x::{Cpu, CpuAddress}, + cpu_common, cpu_common::CpuOption, machine, syntax_token::SyntaxToken, @@ -241,6 +242,7 @@ pub fn update_egui(emu: &mut Emulator, tm: &TimestepManager, elwt: &EventLoopWin None => 0, }; + let cpu_type = emu.machine.cpu().get_type(); let bus = emu.machine.bus_mut(); let mut listview_vec = Vec::new(); @@ -255,7 +257,7 @@ pub fn update_egui(emu: &mut Emulator, tm: &TimestepManager, elwt: &EventLoopWin let mut decode_vec = Vec::new(); - match Cpu::decode(bus, true) { + match cpu_type.decode(bus, true) { Ok(i) => { let instr_slice = bus.get_slice_at(disassembly_addr_flat, i.size as usize); let instr_bytes_str = util::fmt_byte_array(instr_slice); @@ -265,7 +267,7 @@ pub fn update_egui(emu: &mut Emulator, tm: &TimestepManager, elwt: &EventLoopWin format!("{:05X}", disassembly_addr_flat), )); - let mut instr_vec = Cpu::tokenize_instruction(&i); + let mut instr_vec = cpu_type.tokenize_instruction(&i); //let decode_str = format!("{:05X} {:012} {}\n", disassembly_addr, instr_bytes_str, i); @@ -283,7 +285,7 @@ pub fn update_egui(emu: &mut Emulator, tm: &TimestepManager, elwt: &EventLoopWin let new_offset = offset.wrapping_add(i.size as u16); if new_offset < offset { // A wrap of the code segment occurred. Update the linear address to match. - disassembly_addr_flat = Cpu::calc_linear_address(segment, new_offset) as usize; + disassembly_addr_flat = cpu_common::calc_linear_address(segment, new_offset) as usize; } disassembly_addr_seg = Some(CpuAddress::Segmented(segment, new_offset)); diff --git a/frontends/martypc_desktop_wgpu/src/run_benchmark.rs b/frontends/martypc_desktop_wgpu/src/run_benchmark.rs index c4ce3851..00e01ca1 100644 --- a/frontends/martypc_desktop_wgpu/src/run_benchmark.rs +++ b/frontends/martypc_desktop_wgpu/src/run_benchmark.rs @@ -38,9 +38,11 @@ use frontend_common::{ rom_manager::RomManager, BenchmarkEndCondition, }; -use marty_core::bus::ClockFactor; - -use marty_core::machine::{ExecutionControl, ExecutionState, MachineBuilder, MachineRomManifest}; +use marty_core::{ + bus::ClockFactor, + cpu_common::Cpu, + machine::{ExecutionControl, ExecutionState, MachineBuilder, MachineRomManifest}, +}; const BENCHMARK_CYCLE_BATCH: u64 = 100_000; diff --git a/lib/frontend/config_toml_bpaf/src/lib.rs b/lib/frontend/config_toml_bpaf/src/lib.rs index 3c776e38..df611e0e 100644 --- a/lib/frontend/config_toml_bpaf/src/lib.rs +++ b/lib/frontend/config_toml_bpaf/src/lib.rs @@ -37,8 +37,6 @@ use std::{ str::FromStr, }; -use marty_core::{cpu_common::TraceMode, cpu_validator::ValidatorType, machine_types::OnHaltBehavior}; - use frontend_common::{ display_scaler::ScalerPreset, resource_manager::PathConfigItem, @@ -47,6 +45,11 @@ use frontend_common::{ MartyGuiTheme, }; use marty_common::VideoDimensions; +use marty_core::{ + cpu_common::{CpuSubType, CpuType, TraceMode}, + cpu_validator::ValidatorType, + machine_types::OnHaltBehavior, +}; use bpaf::Bpaf; use serde_derive::Deserialize; @@ -196,6 +199,8 @@ pub struct Benchmark { #[derive(Debug, Deserialize)] pub struct Tests { + pub test_cpu_type: Option, + pub test_cpu_subtype: Option, pub test_mode: Option, pub test_seed: Option, pub test_dir: Option, diff --git a/lib/frontend/marty_egui/src/windows/cpu_state_viewer.rs b/lib/frontend/marty_egui/src/windows/cpu_state_viewer.rs index 97aaee90..8dc26d12 100644 --- a/lib/frontend/marty_egui/src/windows/cpu_state_viewer.rs +++ b/lib/frontend/marty_egui/src/windows/cpu_state_viewer.rs @@ -34,7 +34,7 @@ use crate::layouts::MartyLayout; #[allow(dead_code)] use crate::*; use egui::TextBuffer; -use marty_core::cpu_808x::CpuStringState; +use marty_core::cpu_common::CpuStringState; pub struct CpuViewerControl { cpu_state: CpuStringState, From 1935a57f3cc71afc0a3e0017d40c02eba2f44950 Mon Sep 17 00:00:00 2001 From: dbalsom Date: Sun, 5 May 2024 14:34:45 -0400 Subject: [PATCH 02/11] Finish untangling CPUs; add initial V20 --- core/src/arduino8088_validator/mod.rs | 4 +- core/src/arduino8088_validator/remote_cpu.rs | 6 +- core/src/cpu_808x/addressing.rs | 36 +- core/src/cpu_808x/alu.rs | 4 +- core/src/cpu_808x/bitwise.rs | 2 +- core/src/cpu_808x/biu.rs | 6 +- core/src/cpu_808x/cpu.rs | 65 +- core/src/cpu_808x/cycle.rs | 2 +- core/src/cpu_808x/decode.rs | 8 +- core/src/cpu_808x/display.rs | 835 +------ core/src/cpu_808x/execute.rs | 1 + core/src/cpu_808x/fuzzer.rs | 5 +- core/src/cpu_808x/instruction.rs | 23 - core/src/cpu_808x/interrupt.rs | 5 +- core/src/cpu_808x/logging.rs | 4 +- core/src/cpu_808x/mnemonic.rs | 119 - core/src/cpu_808x/mod.rs | 235 +- core/src/cpu_808x/modrm.rs | 3 +- core/src/cpu_808x/stack.rs | 5 +- core/src/cpu_808x/step.rs | 8 +- core/src/cpu_808x/string.rs | 5 +- core/src/cpu_common/addressing.rs | 239 ++ core/src/cpu_common/builder.rs | 21 +- core/src/cpu_common/error.rs | 75 + core/src/cpu_common/instruction.rs | 684 ++++++ core/src/cpu_common/mnemonic.rs | 282 +++ core/src/cpu_common/mod.rs | 158 +- core/src/cpu_common/operands.rs | 59 + core/src/cpu_validator.rs | 2 +- core/src/cpu_vx0/addressing.rs | 437 ++++ core/src/cpu_vx0/alu.rs | 368 +++ core/src/cpu_vx0/bcd.rs | 268 +++ core/src/cpu_vx0/bitwise.rs | 631 +++++ core/src/cpu_vx0/biu.rs | 940 ++++++++ core/src/cpu_vx0/cpu.rs | 525 +++++ core/src/cpu_vx0/cycle.rs | 633 +++++ core/src/cpu_vx0/decode.rs | 786 ++++++ core/src/cpu_vx0/display.rs | 110 + core/src/cpu_vx0/execute.rs | 2100 +++++++++++++++++ core/src/cpu_vx0/fuzzer.rs | 370 +++ core/src/cpu_vx0/gdr.rs | 89 + core/src/cpu_vx0/instruction.rs | 31 + core/src/cpu_vx0/interrupt.rs | 327 +++ core/src/cpu_vx0/jump.rs | 134 ++ core/src/cpu_vx0/logging.rs | 694 ++++++ core/src/cpu_vx0/microcode.rs | 40 + core/src/cpu_vx0/mnemonic.rs | 31 + core/src/cpu_vx0/mod.rs | 1932 +++++++++++++++ core/src/cpu_vx0/modrm.rs | 442 ++++ core/src/cpu_vx0/muldiv.rs | 829 +++++++ core/src/cpu_vx0/queue.rs | 192 ++ core/src/cpu_vx0/stack.rs | 176 ++ core/src/cpu_vx0/step.rs | 625 +++++ core/src/cpu_vx0/string.rs | 339 +++ core/src/lib.rs | 1 + core/src/machine.rs | 8 +- core/src/machine_config.rs | 6 +- .../src/cpu_test/common.rs | 2 +- .../src/cpu_test/gen_tests.rs | 98 +- .../src/cpu_test/process_tests.rs | 88 +- .../src/cpu_test/run_tests.rs | 59 +- .../src/event_loop/egui_update.rs | 4 +- .../martypc_desktop_wgpu/src/run_fuzzer.rs | 73 +- .../marty_egui/src/windows/cpu_control.rs | 2 +- 64 files changed, 14855 insertions(+), 1436 deletions(-) create mode 100644 core/src/cpu_common/addressing.rs create mode 100644 core/src/cpu_common/error.rs create mode 100644 core/src/cpu_common/instruction.rs create mode 100644 core/src/cpu_common/mnemonic.rs create mode 100644 core/src/cpu_common/operands.rs create mode 100644 core/src/cpu_vx0/addressing.rs create mode 100644 core/src/cpu_vx0/alu.rs create mode 100644 core/src/cpu_vx0/bcd.rs create mode 100644 core/src/cpu_vx0/bitwise.rs create mode 100644 core/src/cpu_vx0/biu.rs create mode 100644 core/src/cpu_vx0/cpu.rs create mode 100644 core/src/cpu_vx0/cycle.rs create mode 100644 core/src/cpu_vx0/decode.rs create mode 100644 core/src/cpu_vx0/display.rs create mode 100644 core/src/cpu_vx0/execute.rs create mode 100644 core/src/cpu_vx0/fuzzer.rs create mode 100644 core/src/cpu_vx0/gdr.rs create mode 100644 core/src/cpu_vx0/instruction.rs create mode 100644 core/src/cpu_vx0/interrupt.rs create mode 100644 core/src/cpu_vx0/jump.rs create mode 100644 core/src/cpu_vx0/logging.rs create mode 100644 core/src/cpu_vx0/microcode.rs create mode 100644 core/src/cpu_vx0/mnemonic.rs create mode 100644 core/src/cpu_vx0/mod.rs create mode 100644 core/src/cpu_vx0/modrm.rs create mode 100644 core/src/cpu_vx0/muldiv.rs create mode 100644 core/src/cpu_vx0/queue.rs create mode 100644 core/src/cpu_vx0/stack.rs create mode 100644 core/src/cpu_vx0/step.rs create mode 100644 core/src/cpu_vx0/string.rs diff --git a/core/src/arduino8088_validator/mod.rs b/core/src/arduino8088_validator/mod.rs index 9e39af18..501f1651 100644 --- a/core/src/arduino8088_validator/mod.rs +++ b/core/src/arduino8088_validator/mod.rs @@ -31,7 +31,6 @@ mod udmask; use crate::{ arduino8088_client::*, cpu_808x::{ - QueueOp, CPU_FLAG_AUX_CARRY, CPU_FLAG_CARRY, CPU_FLAG_DIRECTION, @@ -42,6 +41,7 @@ use crate::{ CPU_FLAG_TRAP, CPU_FLAG_ZERO, }, + cpu_common::QueueOp, cpu_validator::*, tracelogger::TraceLogger, }; @@ -737,7 +737,7 @@ impl CpuValidator for ArduinoValidator { //RemoteCpu::print_regs(&self.current_instr.regs[0]); if has_modrm { - if i > (instr.len() - 2) { + if i > (instr.len().saturating_sub(2)) { trace_error!(self, "validate(): modrm specified but instruction length < "); trace_error!( self, diff --git a/core/src/arduino8088_validator/remote_cpu.rs b/core/src/arduino8088_validator/remote_cpu.rs index 5fadd801..82ab43b6 100644 --- a/core/src/arduino8088_validator/remote_cpu.rs +++ b/core/src/arduino8088_validator/remote_cpu.rs @@ -26,12 +26,12 @@ use core::fmt::Display; use std::error::Error; -use crate::cpu_validator::*; - -use super::{BusOp, BusOpType, QueueOp, OPCODE_NOP}; +use super::{BusOp, BusOpType, OPCODE_NOP}; use crate::{ arduino8088_client::*, arduino8088_validator::{queue::*, *}, + cpu_common::QueueOp, + cpu_validator::*, }; macro_rules! trace { diff --git a/core/src/cpu_808x/addressing.rs b/core/src/cpu_808x/addressing.rs index 42df2b76..aa37f2f2 100644 --- a/core/src/cpu_808x/addressing.rs +++ b/core/src/cpu_808x/addressing.rs @@ -30,36 +30,10 @@ */ -use crate::cpu_808x::{biu::*, *}; - -#[derive(Copy, Clone, Debug)] -pub enum AddressingMode { - BxSi, - BxDi, - BpSi, - BpDi, - Si, - Di, - Disp16(Displacement), - Bx, - BxSiDisp8(Displacement), - BxDiDisp8(Displacement), - BpSiDisp8(Displacement), - BpDiDisp8(Displacement), - SiDisp8(Displacement), - DiDisp8(Displacement), - BpDisp8(Displacement), - BxDisp8(Displacement), - BxSiDisp16(Displacement), - BxDiDisp16(Displacement), - BpSiDisp16(Displacement), - BpDiDisp16(Displacement), - SiDisp16(Displacement), - DiDisp16(Displacement), - BpDisp16(Displacement), - BxDisp16(Displacement), - RegisterMode, -} +use crate::{ + cpu_808x::{biu::*, decode::DECODE, *}, + cpu_common::{operands::OperandSize, AddressingMode, OperandType, Segment}, +}; #[derive(Copy, Clone, Debug)] pub enum FarPtr { @@ -158,7 +132,7 @@ impl Intel808x { /// Load the EA operand for the current instruction, if applicable /// (not all instructions with a mod r/m will load, ie, write-only instructions) pub fn load_operand(&mut self) { - if self.i.gdr().loads_ea() { + if DECODE[self.i.decode_idx].gdr.loads_ea() { // This instruction loads its EA operand. Load and save into OPR. let ea_mode: AddressingMode; diff --git a/core/src/cpu_808x/alu.rs b/core/src/cpu_808x/alu.rs index 78adf2c3..c3121c17 100644 --- a/core/src/cpu_808x/alu.rs +++ b/core/src/cpu_808x/alu.rs @@ -32,8 +32,8 @@ #![allow(dead_code)] use crate::{ - cpu_808x::{mnemonic::Mnemonic, *}, - cpu_common::alu::*, + cpu_808x::*, + cpu_common::{alu::*, Mnemonic}, }; /// The ALU operation specifier 'Xi' determines the ALU operation by decoding 5 bits from the group diff --git a/core/src/cpu_808x/bitwise.rs b/core/src/cpu_808x/bitwise.rs index 2711666c..e64d6a1e 100644 --- a/core/src/cpu_808x/bitwise.rs +++ b/core/src/cpu_808x/bitwise.rs @@ -30,7 +30,7 @@ */ -use crate::cpu_808x::{mnemonic::Mnemonic, *}; +use crate::{cpu_808x::*, cpu_common::Mnemonic}; impl Intel808x { pub(crate) fn shl_u8_with_carry(mut byte: u8, mut count: u8) -> (u8, bool) { diff --git a/core/src/cpu_808x/biu.rs b/core/src/cpu_808x/biu.rs index 27987d9e..60751631 100644 --- a/core/src/cpu_808x/biu.rs +++ b/core/src/cpu_808x/biu.rs @@ -29,7 +29,11 @@ Implement CPU behavior specific to the BIU (Bus Interface Unit) */ -use crate::{bytequeue::*, cpu_808x::*}; +use crate::{ + bytequeue::*, + cpu_808x::*, + cpu_common::{operands::OperandSize, QueueOp, Segment}, +}; pub const QUEUE_SIZE: usize = 4; pub const QUEUE_POLICY_LEN: usize = 3; diff --git a/core/src/cpu_808x/cpu.rs b/core/src/cpu_808x/cpu.rs index 01342af7..c04dd34e 100644 --- a/core/src/cpu_808x/cpu.rs +++ b/core/src/cpu_808x/cpu.rs @@ -37,26 +37,37 @@ use crate::{ cpu_808x::{ trace_print, BusStatus, - CpuAddress, - CpuError, CpuState, FetchState, Intel808x, - QueueOp, Register16, - ServiceEvent, - StepResult, TCycle, TaCycle, CPU_FLAGS_RESERVED_ON, }, - cpu_common::{Cpu, CpuDispatch, CpuOption, CpuStringState, CpuType, Instruction, TraceMode}, + cpu_common::{ + instruction::Instruction, + Cpu, + CpuAddress, + CpuDispatch, + CpuError, + CpuOption, + CpuStringState, + CpuType, + QueueOp, + ServiceEvent, + StepResult, + TraceMode, + }, cpu_validator::{CycleState, VRegisters}, syntax_token::SyntaxToken, }; #[cfg(feature = "cpu_validator")] use crate::cpu_808x::CpuValidatorState; +use crate::cpu_common::Register8; +#[cfg(feature = "cpu_validator")] +use crate::cpu_validator::CpuValidator; impl Cpu for Intel808x { fn reset(&mut self) { @@ -263,6 +274,16 @@ impl Cpu for Intel808x { self.set_register16(reg, value); } + #[inline] + fn get_register8(&self, reg: Register8) -> u8 { + self.get_register8(reg) + } + + #[inline] + fn set_register8(&mut self, reg: Register8, value: u8) { + self.set_register8(reg, value); + } + #[inline] fn get_flags(&self) -> u16 { self.get_flags() @@ -471,4 +492,36 @@ impl Cpu for Intel808x { fn trace_flush(&mut self) { self.trace_flush(); } + + // Validation methods + + #[cfg(feature = "cpu_validator")] + fn get_validator(&self) -> &Option> { + self.get_validator() + } + + #[cfg(feature = "cpu_validator")] + fn get_validator_mut(&mut self) -> &mut Option> { + self.get_validator_mut() + } + + fn randomize_seed(&mut self, seed: u64) { + self.randomize_seed(seed); + } + + fn randomize_mem(&mut self) { + self.randomize_mem(); + } + + fn randomize_regs(&mut self) { + self.randomize_regs(); + } + + fn random_grp_instruction(&mut self, opcode: u8, extension_list: &[u8]) { + self.random_grp_instruction(opcode, extension_list) + } + + fn random_inst_from_opcodes(&mut self, opcode_list: &[u8]) { + self.random_inst_from_opcodes(opcode_list); + } } diff --git a/core/src/cpu_808x/cycle.rs b/core/src/cpu_808x/cycle.rs index 8d79dbc4..19c5324a 100644 --- a/core/src/cpu_808x/cycle.rs +++ b/core/src/cpu_808x/cycle.rs @@ -32,7 +32,7 @@ */ -use crate::cpu_808x::*; +use crate::{cpu_808x::*, cpu_common::QueueOp}; #[cfg(feature = "cpu_validator")] use crate::cpu_validator::{BusType, ReadType}; diff --git a/core/src/cpu_808x/decode.rs b/core/src/cpu_808x/decode.rs index 49ca62e5..262aa3f6 100644 --- a/core/src/cpu_808x/decode.rs +++ b/core/src/cpu_808x/decode.rs @@ -37,12 +37,10 @@ use std::{error::Error, fmt::Display}; -use crate::cpu_808x::{addressing::AddressingMode, mnemonic::Mnemonic, modrm::ModRmByte, *}; - use crate::{ bytequeue::*, - cpu_808x::{alu::Xi, gdr::GdrEntry}, - cpu_common::Instruction, + cpu_808x::{alu::Xi, gdr::GdrEntry, modrm::ModRmByte, *}, + cpu_common::{operands::OperandSize, AddressingMode, Instruction, Mnemonic, OperandType, Segment}, }; #[derive(Copy, Clone, PartialEq)] @@ -515,7 +513,7 @@ impl Intel808x { let mut operand2_type: OperandType = OperandType::NoOperand; let mut operand1_size: OperandSize = OperandSize::NoOperand; let mut operand2_size: OperandSize = OperandSize::NoOperand; - + let mut opcode = bytes.q_read_u8(QueueType::First, QueueReader::Biu); let mut size: u32 = 1; let mut op_prefixes: u32 = 0; diff --git a/core/src/cpu_808x/display.rs b/core/src/cpu_808x/display.rs index 0464f56b..e00bafe2 100644 --- a/core/src/cpu_808x/display.rs +++ b/core/src/cpu_808x/display.rs @@ -31,844 +31,13 @@ */ +use crate::cpu_common::Mnemonic; use std::fmt; -use crate::{ - cpu_808x::{addressing::AddressingMode, mnemonic::Mnemonic, *}, - cpu_common::Instruction, -}; +use crate::{cpu_808x::*, cpu_common::instruction::Instruction}; use crate::syntax_token::SyntaxToken; -#[derive(Copy, Clone)] -pub enum OperandSelect { - FirstOperand, - SecondOperand, -} - -fn mnemonic_to_str(op: Mnemonic) -> &'static str { - match op { - Mnemonic::NOP => "NOP", - Mnemonic::AAA => "AAA", - Mnemonic::AAD => "AAD", - Mnemonic::AAM => "AAM", - Mnemonic::AAS => "AAS", - Mnemonic::ADC => "ADC", - Mnemonic::ADD => "ADD", - Mnemonic::AND => "AND", - Mnemonic::CALL => "CALL", - Mnemonic::CALLF => "CALLF", - Mnemonic::CBW => "CBW", - Mnemonic::CLC => "CLC", - Mnemonic::CLD => "CLD", - Mnemonic::CLI => "CLI", - Mnemonic::CMC => "CMC", - Mnemonic::CMP => "CMP", - Mnemonic::CMPSB => "CMPSB", - Mnemonic::CMPSW => "CMPSW", - Mnemonic::CWD => "CWD", - Mnemonic::DAA => "DAA", - Mnemonic::DAS => "DAS", - Mnemonic::DEC => "DEC", - Mnemonic::DIV => "DIV", - Mnemonic::ESC => "ESC", - Mnemonic::WAIT => "WAIT", - Mnemonic::HLT => "HLT", - Mnemonic::IDIV => "IDIV", - Mnemonic::IMUL => "IMUL", - Mnemonic::IN => "IN", - Mnemonic::INC => "INC", - Mnemonic::INT => "INT", - Mnemonic::INT3 => "INT3", - Mnemonic::INTO => "INTO", - Mnemonic::IRET => "IRET", - Mnemonic::JB => "JB", - Mnemonic::JBE => "JBE", - Mnemonic::JCXZ => "JCXZ", - Mnemonic::JL => "JL", - Mnemonic::JLE => "JLE", - Mnemonic::JMP => "JMP", - Mnemonic::JMPF => "JMPF", - Mnemonic::JNB => "JNB", - Mnemonic::JNBE => "JNBE", - Mnemonic::JNL => "JNL", - Mnemonic::JNLE => "JNLE", - Mnemonic::JNO => "JNO", - Mnemonic::JNP => "JNP", - Mnemonic::JNS => "JNS", - Mnemonic::JNZ => "JNZ", - Mnemonic::JO => "JO", - Mnemonic::JP => "JP", - Mnemonic::JS => "JS", - Mnemonic::JZ => "JZ", - Mnemonic::LAHF => "LAHF", - Mnemonic::LDS => "LDS", - Mnemonic::LEA => "LEA", - Mnemonic::LES => "LES", - Mnemonic::LOCK => "LOCK", - Mnemonic::LODSB => "LODSB", - Mnemonic::LODSW => "LODSW", - Mnemonic::LOOP => "LOOP", - Mnemonic::LOOPNE => "LOOPNE", - Mnemonic::LOOPE => "LOOPE", - Mnemonic::MOV => "MOV", - Mnemonic::MOVSB => "MOVSB", - Mnemonic::MOVSW => "MOVSW", - Mnemonic::MUL => "MUL", - Mnemonic::NEG => "NEG", - Mnemonic::NOT => "NOT", - Mnemonic::OR => "OR", - Mnemonic::OUT => "OUT", - Mnemonic::POP => "POP", - Mnemonic::POPF => "POPF", - Mnemonic::PUSH => "PUSH", - Mnemonic::PUSHF => "PUSHF", - Mnemonic::RCL => "RCL", - Mnemonic::RCR => "RCR", - Mnemonic::REP => "REP", - Mnemonic::REPNE => "REPNE", - Mnemonic::REPE => "REPE", - Mnemonic::RETF => "RETF", - Mnemonic::RETN => "RETN", - Mnemonic::ROL => "ROL", - Mnemonic::ROR => "ROR", - Mnemonic::SAHF => "SAHF", - Mnemonic::SALC => "SALC", - Mnemonic::SAR => "SAR", - Mnemonic::SBB => "SBB", - Mnemonic::SCASB => "SCASB", - Mnemonic::SCASW => "SCASW", - Mnemonic::SETMO => "SETMO", - Mnemonic::SETMOC => "SETMOC", - Mnemonic::SHL => "SHL", - Mnemonic::SHR => "SHR", - Mnemonic::STC => "STC", - Mnemonic::STD => "STD", - Mnemonic::STI => "STI", - Mnemonic::STOSB => "STOSB", - Mnemonic::STOSW => "STOSW", - Mnemonic::SUB => "SUB", - Mnemonic::TEST => "TEST", - Mnemonic::XCHG => "XCHG", - Mnemonic::XLAT => "XLAT", - Mnemonic::XOR => "XOR", - _ => "INVALID", - } -} - -impl fmt::Display for Mnemonic { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", mnemonic_to_str(*self)) - } -} - -struct SignedHex(T); - -struct WithPlusSign(T); -struct WithSign(T); - -impl fmt::Display for Displacement { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Displacement::Pending8 | Displacement::Pending16 | Displacement::NoDisp => { - write!(f, "Invalid Displacement") - } - Displacement::Disp8(i) => { - write!(f, "{:X}h", i) - } - Displacement::Disp16(i) => { - write!(f, "{:X}h", i) - } - } - } -} - -impl fmt::Display for SignedHex { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.0 { - Displacement::Pending8 | Displacement::Pending16 | Displacement::NoDisp => { - write!(f, "Invalid Displacement") - } - Displacement::Disp8(i) => { - if *i < 0 { - write!(f, "{:X}h", !i.wrapping_sub(1)) - } - else { - write!(f, "{:X}h", i) - } - } - Displacement::Disp16(i) => { - if *i < 0 { - write!(f, "{:X}h", !i.wrapping_sub(1)) - } - else { - write!(f, "{:X}h", i) - } - } - } - } -} - -impl Display for WithPlusSign { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.0 { - Displacement::Pending8 | Displacement::Pending16 | Displacement::NoDisp => { - write!(f, "Invalid Displacement") - } - Displacement::Disp8(i) => { - if *i < 0 { - write!(f, "-{}", SignedHex(self.0)) - } - else { - write!(f, "+{}", SignedHex(self.0)) - } - } - Displacement::Disp16(i) => { - if *i < 0 { - write!(f, "-{}", SignedHex(self.0)) - } - else { - write!(f, "+{}", SignedHex(self.0)) - } - } - } - } -} - -impl Display for WithSign { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.0 { - Displacement::Pending8 | Displacement::Pending16 | Displacement::NoDisp => { - write!(f, "Invalid Displacement") - } - Displacement::Disp8(i) => { - if *i < 0 { - write!(f, "-{}", SignedHex(self.0)) - } - else { - write!(f, "{}", SignedHex(self.0)) - } - } - Displacement::Disp16(i) => { - if *i < 0 { - write!(f, "-{}", SignedHex(self.0)) - } - else { - write!(f, "{}", SignedHex(self.0)) - } - } - } - } -} - -impl fmt::Display for Instruction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut instruction_string = String::new(); - - // Stick segment override prefix on certain opcodes (string ops) - let sego_prefix = override_prefix_to_string(self); - if let Some(so) = sego_prefix { - instruction_string.push_str(&so); - instruction_string.push_str(" "); - } - - // Add other prefixes (rep(x), lock, etc) - let prefix = prefix_to_string(self); - let mnemonic = mnemonic_to_str(self.mnemonic).to_string().to_lowercase(); - - if let Some(p) = prefix { - instruction_string.push_str(&p); - instruction_string.push_str(" "); - } - - instruction_string.push_str(&mnemonic); - - // Dont sign-extend 8-bit port addresses. - let op_size = match self.mnemonic { - Mnemonic::IN | Mnemonic::OUT => OperandSize::Operand8, - _ => self.operand1_size, - }; - - let op1 = operand_to_string(self, OperandSelect::FirstOperand, op_size); - if op1.len() > 0 { - instruction_string.push_str(" "); - instruction_string.push_str(&op1); - } - - let op2: String = operand_to_string(self, OperandSelect::SecondOperand, op_size); - if op2.len() > 0 { - instruction_string.push_str(", "); - instruction_string.push_str(&op2); - } - - write!(f, "{}", instruction_string) - } -} - -impl Intel808x { - pub fn tokenize_instruction(i: &Instruction) -> Vec { - // Dont sign-extend 8-bit port addresses. - let op_size = match i.mnemonic { - Mnemonic::IN | Mnemonic::OUT => OperandSize::Operand8, - _ => i.operand1_size, - }; - - let mut i_vec = SyntaxTokenVec(Vec::new()); - - // Stick segment override prefix on certain opcodes (string ops) - let sego_prefix = override_prefix_to_string(i); - if let Some(so) = sego_prefix { - i_vec.0.push(SyntaxToken::Prefix(so)); - i_vec.0.push(SyntaxToken::Formatter(SyntaxFormatType::Space)); - } - - let prefix = prefix_to_string(i); - if let Some(p) = prefix { - i_vec.0.push(SyntaxToken::Prefix(p)); - i_vec.0.push(SyntaxToken::Formatter(SyntaxFormatType::Space)); - } - - let mnemonic = mnemonic_to_str(i.mnemonic).to_string().to_lowercase(); - i_vec.0.push(SyntaxToken::Mnemonic(mnemonic)); - - let op1_vec = tokenize_operand(i, OperandSelect::FirstOperand, op_size); - i_vec.append(op1_vec, Some(SyntaxToken::Formatter(SyntaxFormatType::Space)), None); - - let op2_vec = tokenize_operand(i, OperandSelect::SecondOperand, op_size); - - if !op2_vec.is_empty() { - i_vec.0.push(SyntaxToken::Comma); - i_vec.append(op2_vec, Some(SyntaxToken::Formatter(SyntaxFormatType::Space)), None); - } - - i_vec.0 - } -} - -impl SyntaxTokenize for Instruction { - fn tokenize(&self) -> Vec { - Intel808x::tokenize_instruction(self) - } -} - -struct Imm8Extend(u8); -struct Imm8sExtend(i8); -struct Rel8Extend(i8); - -impl fmt::UpperHex for Imm8sExtend { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let prefix = if f.alternate() { "0x" } else { "" }; - // Check if the value is negative (the top bit of an i8 is set) - if self.0 < 0 { - // If it's negative, sign-extend with FF - let bare_hex = format!("FF{:2X}", self.0 as u8); - f.pad_integral(true, prefix, &bare_hex) - } - else { - // If it's positive or zero, simply show the original byte - let bare_hex = format!("{:X}", self.0 as u8); - f.pad_integral(true, prefix, &bare_hex) - } - } -} - -impl fmt::UpperHex for Imm8Extend { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let prefix = if f.alternate() { "0x" } else { "" }; - // Check if the highest bit (bit 7) is set - if self.0 & 0x80 != 0 { - // If it's set, sign-extend with FF - let bare_hex = format!("FF{:02X}", self.0); - f.pad_integral(true, prefix, &bare_hex) - } - else { - // If it's not set, simply show the original byte - let bare_hex = format!("{:02X}", self.0); - f.pad_integral(true, prefix, &bare_hex) - } - } -} - -impl fmt::UpperHex for Rel8Extend { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let prefix = if f.alternate() { "0x" } else { "" }; - // Check if the value is negative (the top bit of an i8 is set) - if self.0 < 0 { - // If it's negative, sign-extend with FF - let bare_hex = format!("FF{:02X}", self.0 as u8); - f.pad_integral(true, prefix, &bare_hex) - } - else { - // If it's positive or zero, simply show the original byte - let bare_hex = format!("00{:02X}", self.0 as u8); - f.pad_integral(true, prefix, &bare_hex) - } - } -} - -fn operand_to_string(i: &Instruction, op: OperandSelect, lvalue: OperandSize) -> String { - let (op_type, op_size) = match op { - OperandSelect::FirstOperand => (i.operand1_type, i.operand1_size), - OperandSelect::SecondOperand => (i.operand2_type, i.operand2_size), - }; - - let instruction_string: String = match op_type { - OperandType::Immediate8(imm8) => { - if let OperandSize::Operand8 = lvalue { - format!("{:X}h", imm8) - } - else { - format!("{:X}h", Imm8Extend(imm8)) - } - } - OperandType::Immediate8s(imm8) => { - // imm8 is always sign-extended to 16 - format!("{:X}h", Imm8sExtend(imm8)) - } - OperandType::Immediate16(imm16) => { - format!("{:X}h", imm16) - } - OperandType::Relative8(rel8) => { - //format!("short {:04X}h", i.size as i16 + rel8 as i16) - format!("{:04X}h", i.size as i16 + rel8 as i16) - } - OperandType::Relative16(rel16) => { - //format!("short {:04X}h", i.size as i16 + rel16) - format!("{:04X}h", i.size as i16 + rel16) - } - OperandType::Offset8(offset8) => { - let segment: String = match i.segment_override { - Some(Segment::ES) => "es".to_string(), - Some(Segment::CS) => "cs".to_string(), - Some(Segment::SS) => "ss".to_string(), - _ => "ds".to_string(), - }; - format!("byte [{}:{:X}h]", segment, offset8) - } - OperandType::Offset16(offset16) => { - let segment: String = match i.segment_override { - Some(Segment::ES) => "es".to_string(), - Some(Segment::CS) => "cs".to_string(), - Some(Segment::SS) => "ss".to_string(), - _ => "ds".to_string(), - }; - format!("word [{}:{:X}h]", segment, offset16) - } - OperandType::Register8(reg8) => match reg8 { - Register8::AL => "al".to_string(), - Register8::CL => "cl".to_string(), - Register8::DL => "dl".to_string(), - Register8::BL => "bl".to_string(), - Register8::AH => "ah".to_string(), - Register8::CH => "ch".to_string(), - Register8::DH => "dh".to_string(), - Register8::BH => "bh".to_string(), - }, - OperandType::Register16(reg16) => match reg16 { - Register16::AX => "ax".to_string(), - Register16::CX => "cx".to_string(), - Register16::DX => "dx".to_string(), - Register16::BX => "bx".to_string(), - Register16::SP => "sp".to_string(), - Register16::BP => "bp".to_string(), - Register16::SI => "si".to_string(), - Register16::DI => "di".to_string(), - Register16::ES => "es".to_string(), - Register16::CS => "cs".to_string(), - Register16::SS => "ss".to_string(), - Register16::DS => "ds".to_string(), - _ => "".to_string(), - }, - OperandType::AddressingMode(addr_mode) => { - let mut ptr_prefix: String = match op_size { - OperandSize::Operand8 => "byte ".to_string(), - OperandSize::Operand16 => "word ".to_string(), - OperandSize::NoOperand => "*invalid ptr* ".to_string(), - OperandSize::NoSize => "".to_string(), - }; - // LEA uses addressing calculations but isn't actually a pointer - if let Mnemonic::LEA = i.mnemonic { - ptr_prefix = "".to_string() - } - // LES and LDS point to a DWORD address - if let Mnemonic::LES | Mnemonic::LDS = i.mnemonic { - ptr_prefix = "dword ".to_string() - } - - let mut segment1 = "ds".to_string(); - let mut segment2 = "ss".to_string(); - - // Handle segment override prefixes - match i.segment_override { - Some(Segment::ES) => { - segment1 = "es".to_string(); - segment2 = "es".to_string(); - } - Some(Segment::CS) => { - segment1 = "cs".to_string(); - segment2 = "cs".to_string(); - } - Some(Segment::SS) => { - segment1 = "ss".to_string(); - segment2 = "ss".to_string(); - } - Some(Segment::DS) => { - segment1 = "ds".to_string(); - segment2 = "ds".to_string(); - } - _ => {} - } - - match addr_mode { - AddressingMode::BxSi => format!("{}[{}:bx+si]", ptr_prefix, segment1), - AddressingMode::BxDi => format!("{}[{}:bx+di]", ptr_prefix, segment1), - AddressingMode::BpSi => format!("{}[{}:bp+si]", ptr_prefix, segment2), - AddressingMode::BpDi => format!("{}[{}:bp+di]", ptr_prefix, segment2), - AddressingMode::Si => format!("{}[{}:si]", ptr_prefix, segment1), - AddressingMode::Di => format!("{}[{}:di]", ptr_prefix, segment1), - AddressingMode::Disp16(disp) => format!("{}[{}:{}]", ptr_prefix, segment1, disp), - AddressingMode::Bx => format!("{}[{}:bx]", ptr_prefix, segment1), - AddressingMode::BxSiDisp8(disp) => { - format!("{}[{}:bx+si{}]", ptr_prefix, segment1, WithPlusSign(disp)) - } - AddressingMode::BxDiDisp8(disp) => { - format!("{}[{}:bx+di{}]", ptr_prefix, segment1, WithPlusSign(disp)) - } - AddressingMode::BpSiDisp8(disp) => { - format!("{}[{}:bp+si{}]", ptr_prefix, segment2, WithPlusSign(disp)) - } - AddressingMode::BpDiDisp8(disp) => { - format!("{}[{}:bp+di{}]", ptr_prefix, segment2, WithPlusSign(disp)) - } - AddressingMode::SiDisp8(disp) => { - format!("{}[{}:si{}]", ptr_prefix, segment1, WithPlusSign(disp)) - } - AddressingMode::DiDisp8(disp) => { - format!("{}[{}:di{}]", ptr_prefix, segment1, WithPlusSign(disp)) - } - AddressingMode::BpDisp8(disp) => { - format!("{}[{}:bp{}]", ptr_prefix, segment2, WithPlusSign(disp)) - } - AddressingMode::BxDisp8(disp) => { - format!("{}[{}:bx{}]", ptr_prefix, segment1, WithPlusSign(disp)) - } - AddressingMode::BxSiDisp16(disp) => { - format!("{}[{}:bx+si{}]", ptr_prefix, segment1, WithPlusSign(disp)) - } - AddressingMode::BxDiDisp16(disp) => { - format!("{}[{}:bx+di{}]", ptr_prefix, segment1, WithPlusSign(disp)) - } - AddressingMode::BpSiDisp16(disp) => { - format!("{}[{}:bp+si{}]", ptr_prefix, segment2, WithPlusSign(disp)) - } - AddressingMode::BpDiDisp16(disp) => { - format!("{}[{}:bp+di{}]", ptr_prefix, segment2, WithPlusSign(disp)) - } - AddressingMode::SiDisp16(disp) => { - format!("{}[{}:si{}]", ptr_prefix, segment1, WithPlusSign(disp)) - } - AddressingMode::DiDisp16(disp) => { - format!("{}[{}:di{}]", ptr_prefix, segment1, WithPlusSign(disp)) - } - AddressingMode::BpDisp16(disp) => { - format!("{}[{}:bp{}]", ptr_prefix, segment2, WithPlusSign(disp)) - } - AddressingMode::BxDisp16(disp) => { - format!("{}[{}:bx{}]", ptr_prefix, segment1, WithPlusSign(disp)) - } - AddressingMode::RegisterMode => "".to_string(), - } - } - /* - OperandType::NearAddress(offset) => { - format!("[{:#06X}]", offset) - } - */ - OperandType::FarAddress(segment, offset) => { - format!("{:04X}h:{:04X}h", segment, offset) - } - OperandType::NoOperand => "".to_string(), - _ => "".to_string(), - }; - - instruction_string -} - -fn tokenize_operand(i: &Instruction, op: OperandSelect, lvalue: OperandSize) -> Vec { - let (op_type, op_size) = match op { - OperandSelect::FirstOperand => (i.operand1_type, i.operand1_size), - OperandSelect::SecondOperand => (i.operand2_type, i.operand2_size), - }; - - let mut op_vec = Vec::new(); - - match op_type { - OperandType::Immediate8(imm8) => { - if let OperandSize::Operand8 = lvalue { - op_vec.push(SyntaxToken::HexValue(format!("{:X}h", imm8))); - } - else { - op_vec.push(SyntaxToken::HexValue(format!("{:X}h", Imm8Extend(imm8)))); - } - } - OperandType::Immediate8s(imm8s) => { - op_vec.push(SyntaxToken::HexValue(format!("{:X}h", Imm8sExtend(imm8s)))); - } - OperandType::Immediate16(imm16) => { - op_vec.push(SyntaxToken::HexValue(format!("{:X}h", imm16))); - } - OperandType::Relative8(rel8) => { - //op_vec.push(SyntaxToken::Text("short".to_string())); - //op_vec.push(SyntaxToken::Formatter(SyntaxFormatType::Space)); - op_vec.push(SyntaxToken::HexValue(format!("{:04X}h", i.size as i16 + rel8 as i16))); - } - OperandType::Relative16(rel16) => { - //op_vec.push(SyntaxToken::Text("short".to_string())); - //op_vec.push(SyntaxToken::Formatter(SyntaxFormatType::Space)); - op_vec.push(SyntaxToken::HexValue(format!("{:04X}h", i.size as i16 + rel16))); - } - OperandType::Offset8(offset8) => { - let segment: String = match i.segment_override { - Some(Segment::ES) => "es".to_string(), - Some(Segment::CS) => "cs".to_string(), - Some(Segment::SS) => "ss".to_string(), - _ => "ds".to_string(), - }; - op_vec.push(SyntaxToken::Text("byte".to_string())); - op_vec.push(SyntaxToken::Formatter(SyntaxFormatType::Space)); - op_vec.push(SyntaxToken::OpenBracket); - op_vec.push(SyntaxToken::Segment(segment)); - op_vec.push(SyntaxToken::Colon); - op_vec.push(SyntaxToken::HexValue(format!("{:X}h", offset8))); - op_vec.push(SyntaxToken::CloseBracket); - } - OperandType::Offset16(offset16) => { - let segment: String = match i.segment_override { - Some(Segment::ES) => "es".to_string(), - Some(Segment::CS) => "cs".to_string(), - Some(Segment::SS) => "ss".to_string(), - _ => "ds".to_string(), - }; - op_vec.push(SyntaxToken::Text("word".to_string())); - op_vec.push(SyntaxToken::Formatter(SyntaxFormatType::Space)); - op_vec.push(SyntaxToken::OpenBracket); - op_vec.push(SyntaxToken::Segment(segment)); - op_vec.push(SyntaxToken::Colon); - op_vec.push(SyntaxToken::HexValue(format!("{:X}h", offset16))); - op_vec.push(SyntaxToken::CloseBracket); - } - OperandType::Register8(reg8) => { - let reg = match reg8 { - Register8::AL => "al".to_string(), - Register8::CL => "cl".to_string(), - Register8::DL => "dl".to_string(), - Register8::BL => "bl".to_string(), - Register8::AH => "ah".to_string(), - Register8::CH => "ch".to_string(), - Register8::DH => "dh".to_string(), - Register8::BH => "bh".to_string(), - }; - op_vec.push(SyntaxToken::Register(reg)); - } - OperandType::Register16(reg16) => { - let reg = match reg16 { - Register16::AX => "ax".to_string(), - Register16::CX => "cx".to_string(), - Register16::DX => "dx".to_string(), - Register16::BX => "bx".to_string(), - Register16::SP => "sp".to_string(), - Register16::BP => "bp".to_string(), - Register16::SI => "si".to_string(), - Register16::DI => "di".to_string(), - Register16::ES => "es".to_string(), - Register16::CS => "cs".to_string(), - Register16::SS => "ss".to_string(), - Register16::DS => "ds".to_string(), - _ => "".to_string(), - }; - op_vec.push(SyntaxToken::Register(reg)); - } - OperandType::AddressingMode(addr_mode) => { - let mut ptr_prefix: Option = match op_size { - OperandSize::Operand8 => Some("byte".to_string()), - OperandSize::Operand16 => Some("word".to_string()), - OperandSize::NoOperand => Some("*invalid*".to_string()), - OperandSize::NoSize => None, - }; - // LEA uses addressing calculations but isn't actually a pointer - if let Mnemonic::LEA = i.mnemonic { - ptr_prefix = None - } - // LES and LDS point to a DWORD address - if let Mnemonic::LES | Mnemonic::LDS = i.mnemonic { - ptr_prefix = Some("dword".to_string()) - } - - // Add pointer prefix, if any - if let Some(prefix) = ptr_prefix { - op_vec.push(SyntaxToken::Text(prefix)); - op_vec.push(SyntaxToken::Formatter(SyntaxFormatType::Space)) - } - - let mut segment1 = "ds".to_string(); - let mut segment2 = "ss".to_string(); - - // Handle segment override prefixes - match i.segment_override { - Some(Segment::ES) => { - segment1 = "es".to_string(); - segment2 = "es".to_string(); - } - Some(Segment::CS) => { - segment1 = "cs".to_string(); - segment2 = "cs".to_string(); - } - Some(Segment::SS) => { - segment1 = "ss".to_string(); - segment2 = "ss".to_string(); - } - Some(Segment::DS) => { - segment1 = "ds".to_string(); - segment2 = "ds".to_string(); - } - _ => {} - } - - let segment1_token = SyntaxToken::Segment(segment1); - let segment2_token = SyntaxToken::Segment(segment2); - - let mut have_addr_mode = true; - - let (seg_token, disp_opt, ea_vec) = match addr_mode { - AddressingMode::BxSi => (segment1_token, None, ["bx", "si"]), - AddressingMode::BxDi => (segment1_token, None, ["bx", "di"]), - AddressingMode::BpSi => (segment2_token, None, ["bp", "si"]), - AddressingMode::BpDi => (segment2_token, None, ["bp", "di"]), - AddressingMode::Si => (segment1_token, None, ["si", ""]), - AddressingMode::Di => (segment1_token, None, ["di", ""]), - AddressingMode::Disp16(disp) => (segment1_token, Some(disp), ["", ""]), - AddressingMode::Bx => (segment1_token, None, ["bx", ""]), - AddressingMode::BxSiDisp8(disp) => (segment1_token, Some(disp), ["bx", "si"]), - AddressingMode::BxDiDisp8(disp) => (segment1_token, Some(disp), ["bx", "di"]), - AddressingMode::BpSiDisp8(disp) => (segment2_token, Some(disp), ["bp", "si"]), - AddressingMode::BpDiDisp8(disp) => (segment2_token, Some(disp), ["bp", "di"]), - AddressingMode::SiDisp8(disp) => (segment1_token, Some(disp), ["si", ""]), - AddressingMode::DiDisp8(disp) => (segment1_token, Some(disp), ["di", ""]), - AddressingMode::BpDisp8(disp) => (segment2_token, Some(disp), ["bp", ""]), - AddressingMode::BxDisp8(disp) => (segment1_token, Some(disp), ["bx", ""]), - AddressingMode::BxSiDisp16(disp) => (segment1_token, Some(disp), ["bx", "si"]), - AddressingMode::BxDiDisp16(disp) => (segment1_token, Some(disp), ["bx", "di"]), - AddressingMode::BpSiDisp16(disp) => (segment2_token, Some(disp), ["bp", "si"]), - AddressingMode::BpDiDisp16(disp) => (segment2_token, Some(disp), ["bp", "di"]), - AddressingMode::SiDisp16(disp) => (segment1_token, Some(disp), ["si", ""]), - AddressingMode::DiDisp16(disp) => (segment1_token, Some(disp), ["di", ""]), - AddressingMode::BpDisp16(disp) => (segment2_token, Some(disp), ["bp", ""]), - AddressingMode::BxDisp16(disp) => (segment1_token, Some(disp), ["bx", ""]), - AddressingMode::RegisterMode => { - have_addr_mode = false; - (segment1_token, None, ["", ""]) - } - }; - - if have_addr_mode { - op_vec.push(SyntaxToken::OpenBracket); - op_vec.push(seg_token); - op_vec.push(SyntaxToken::Colon); - - if ea_vec[0].len() > 0 { - // Have first component of ea - op_vec.push(SyntaxToken::Register(ea_vec[0].to_string())); - } - else if let Some(disp) = disp_opt { - // Displacement by itself - op_vec.push(SyntaxToken::Displacement(format!("{}", disp))); - } - - if ea_vec[1].len() > 0 { - // Have second component of ea - op_vec.push(SyntaxToken::PlusSign); - op_vec.push(SyntaxToken::Register(ea_vec[1].to_string())); - } - - if ea_vec[0].len() > 0 { - // Have at least one ea component. Add +displacement if present. - if let Some(disp) = disp_opt { - // TODO: Generate +/- as tokens for displacement? - op_vec.push(SyntaxToken::Displacement(format!("{}", WithPlusSign(disp)))); - } - } - - op_vec.push(SyntaxToken::CloseBracket); - } - } - /* - OperandType::NearAddress(offset) => { - - op_vec.push(SyntaxToken::OpenBracket); - op_vec.push(SyntaxToken::HexValue(format!("{:04X}h", offset))); - op_vec.push(SyntaxToken::CloseBracket); - } - */ - OperandType::FarAddress(segment, offset) => { - op_vec.push(SyntaxToken::HexValue(format!("{:04X}h", segment))); - op_vec.push(SyntaxToken::Colon); - op_vec.push(SyntaxToken::HexValue(format!("{:04X}h", offset))); - } - _ => {} - }; - - op_vec -} - -fn override_prefix_to_string(i: &Instruction) -> Option { - if i.segment_override.is_some() { - match i.opcode { - 0xA4 | 0xA5 | 0xAA | 0xAB | 0xAC | 0xAD | 0xA6 | 0xA7 | 0xAE | 0xAF => { - let segment: String = match i.segment_override { - Some(Segment::ES) => "es".to_string(), - Some(Segment::CS) => "cs".to_string(), - Some(Segment::SS) => "ss".to_string(), - _ => "ds".to_string(), - }; - Some(segment) - } - _ => None, - } - } - else { - // No override - None - } -} - -fn prefix_to_string(i: &Instruction) -> Option { - // Handle REPx prefixes - // TODO: IS F2 valid on 6C, 6D, etc? - - if i.prefixes & OPCODE_PREFIX_LOCK != 0 { - Some("lock".to_string()) - } - else if i.prefixes & OPCODE_PREFIX_REP1 != 0 { - match i.opcode { - 0xF6 | 0xF7 => None, // Don't show REP prefix on div. - 0xA4 | 0xA5 | 0xAA | 0xAB | 0xAC | 0xAD => Some("rep".to_string()), - 0xA6 | 0xA7 | 0xAE | 0xAF => Some("repne".to_string()), - _ => None, - } - } - else if i.prefixes & OPCODE_PREFIX_REP2 != 0 { - match i.opcode { - 0xF6 | 0xF7 => None, // Don't show REP prefix on div. - 0xA4 | 0xA5 | 0xAA | 0xAB | 0xAC | 0xAD => Some("rep".to_string()), - 0xA6 | 0xA7 | 0xAE | 0xAF => Some("repe".to_string()), - _ => None, - } - } - else { - None - } -} - #[cfg(test)] mod tests { #[cfg(feature = "cpu_validator")] diff --git a/core/src/cpu_808x/execute.rs b/core/src/cpu_808x/execute.rs index a116ec67..3ff6f86d 100644 --- a/core/src/cpu_808x/execute.rs +++ b/core/src/cpu_808x/execute.rs @@ -32,6 +32,7 @@ use crate::{ cpu_808x::{biu::*, *}, + cpu_common::{CpuAddress, CpuException, ExecutionResult, Mnemonic, OperandType, QueueOp, Segment}, util, }; diff --git a/core/src/cpu_808x/fuzzer.rs b/core/src/cpu_808x/fuzzer.rs index 76b22659..8f988c97 100644 --- a/core/src/cpu_808x/fuzzer.rs +++ b/core/src/cpu_808x/fuzzer.rs @@ -33,7 +33,10 @@ use rand::{Rng, SeedableRng}; //use rand::rngs::StdRng; -use crate::cpu_808x::{modrm::MODRM_REG_MASK, *}; +use crate::{ + cpu_808x::{modrm::MODRM_REG_MASK, *}, + cpu_common::{CpuAddress, Segment}, +}; const RNG_SEED: u64 = 0x58158258u64; diff --git a/core/src/cpu_808x/instruction.rs b/core/src/cpu_808x/instruction.rs index c24ffc25..abecca06 100644 --- a/core/src/cpu_808x/instruction.rs +++ b/core/src/cpu_808x/instruction.rs @@ -29,26 +29,3 @@ Definition of the Instruction struct and related methods. */ - -use crate::{ - cpu_808x::{ - decode::{InstTemplate, DECODE}, - gdr::GdrEntry, - mnemonic::Mnemonic, - OperandSize, - OperandType, - Segment, - }, - cpu_common::Instruction, -}; - -impl Instruction { - #[inline(always)] - pub fn decode_ref(&self) -> &InstTemplate { - &DECODE[self.decode_idx] - } - #[inline(always)] - pub fn gdr(&self) -> &GdrEntry { - &self.decode_ref().gdr - } -} diff --git a/core/src/cpu_808x/interrupt.rs b/core/src/cpu_808x/interrupt.rs index 6bb54ae8..6883bc86 100644 --- a/core/src/cpu_808x/interrupt.rs +++ b/core/src/cpu_808x/interrupt.rs @@ -30,7 +30,10 @@ */ -use crate::cpu_808x::*; +use crate::{ + cpu_808x::*, + cpu_common::{Segment, ServiceEvent}, +}; impl Intel808x { /// Execute the IRET microcode routine. diff --git a/core/src/cpu_808x/logging.rs b/core/src/cpu_808x/logging.rs index 78fb4158..94755d95 100644 --- a/core/src/cpu_808x/logging.rs +++ b/core/src/cpu_808x/logging.rs @@ -37,8 +37,6 @@ use crate::{ Cpu, DmaState, Intel808x, - QueueOp, - Segment, TCycle, TaCycle, CPU_FLAG_AUX_CARRY, @@ -51,7 +49,7 @@ use crate::{ CPU_FLAG_TRAP, CPU_FLAG_ZERO, }, - cpu_common::TraceMode, + cpu_common::{QueueOp, Segment, TraceMode}, syntax_token::SyntaxToken, }; diff --git a/core/src/cpu_808x/mnemonic.rs b/core/src/cpu_808x/mnemonic.rs index fb655c7f..4ec4c419 100644 --- a/core/src/cpu_808x/mnemonic.rs +++ b/core/src/cpu_808x/mnemonic.rs @@ -29,122 +29,3 @@ Defines mnemonic enum. */ - -#[allow(dead_code)] -#[derive(PartialEq, Copy, Clone, Debug)] -pub enum Mnemonic { - InvalidOpcode, - NoOpcode, - Group, - Prefix, - NOP, - AAA, - AAD, - AAM, - AAS, - ADC, - ADD, - AND, - CALL, - CALLF, - CBW, - CLC, - CLD, - CLI, - CMC, - CMP, - CMPSB, - CMPSW, - CWD, - DAA, - DAS, - DEC, - DIV, - ESC, - WAIT, - HLT, - IDIV, - IMUL, - IN, - INC, - INT, - INT3, - INTO, - IRET, - JB, - JBE, - JCXZ, - JL, - JLE, - JMP, - JMPF, - JNB, - JNBE, - JNL, - JNLE, - JNO, - JNP, - JNS, - JNZ, - JO, - JP, - JS, - JZ, - LAHF, - LDS, - LEA, - LES, - LOCK, - LODSB, - LODSW, - LOOP, - LOOPNE, - LOOPE, - MOV, - MOVSB, - MOVSW, - MUL, - NEG, - NOT, - OR, - OUT, - POP, - POPF, - PUSH, - PUSHF, - RCL, - RCR, - REP, - REPNE, - REPE, - RETF, - RETN, - ROL, - ROR, - SAHF, - SALC, - SAR, - SBB, - SCASB, - SCASW, - SETMO, - SETMOC, - SHL, - SHR, - STC, - STD, - STI, - STOSB, - STOSW, - SUB, - TEST, - XCHG, - XLAT, - XOR, -} - -impl Default for Mnemonic { - fn default() -> Self { - Mnemonic::InvalidOpcode - } -} diff --git a/core/src/cpu_808x/mod.rs b/core/src/cpu_808x/mod.rs index a54adfe3..c59231a5 100644 --- a/core/src/cpu_808x/mod.rs +++ b/core/src/cpu_808x/mod.rs @@ -33,7 +33,18 @@ #![allow(clippy::unusual_byte_groupings)] pub use crate::cpu_common::Cpu; -use crate::cpu_common::{CpuStringState, CpuSubType, Instruction}; +use crate::cpu_common::{ + instruction::Instruction, + AddressingMode, + CpuAddress, + CpuStringState, + CpuSubType, + ExecutionResult, + Mnemonic, + QueueOp, + Segment, + ServiceEvent, +}; use core::fmt::Display; use lazy_static::lazy_static; use regex::Regex; @@ -69,7 +80,7 @@ use crate::{ breakpoints::{BreakPointType, CycleStopWatch, StopWatchData}, bus::{BusInterface, MEM_BPA_BIT, MEM_BPE_BIT, MEM_RET_BIT, MEM_SW_BIT}, bytequeue::*, - cpu_808x::{addressing::AddressingMode, microcode::*, mnemonic::Mnemonic, queue::InstructionQueue}, + cpu_808x::{microcode::*, queue::InstructionQueue}, cpu_common::{CpuOption, CpuType, TraceMode}, syntax_token::*, tracelogger::TraceLogger, @@ -110,7 +121,14 @@ macro_rules! trace_print { }}; } -use crate::cpu_common::{Register16, Register8}; +#[macro_export] +macro_rules! gdr { + ($inst:expr) => { + &DECODE[$inst.decode_idx as usize].gdr + }; +} + +use crate::cpu_common::{operands::OperandSize, Register16, Register8}; use trace_print; const QUEUE_MAX: usize = 6; @@ -272,12 +290,6 @@ pub const REGISTER16_LUT: [Register16; 8] = [ pub const SEGMENT_REGISTER16_LUT: [Register16; 4] = [Register16::ES, Register16::CS, Register16::SS, Register16::DS]; -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum CpuException { - NoException, - DivideError, -} - #[derive(Debug, Copy, Clone, PartialEq)] pub enum CpuState { Normal, @@ -289,55 +301,6 @@ impl Default for CpuState { } } -#[derive(Debug)] -pub enum CpuError { - InvalidInstructionError(u8, u32), - UnhandledInstructionError(u8, u32), - InstructionDecodeError(u32), - ExecutionError(u32, String), - CpuHaltedError(u32), - ExceptionError(CpuException), -} -impl Error for CpuError {} -impl Display for CpuError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &*self { - CpuError::InvalidInstructionError(o, addr) => write!( - f, - "An invalid instruction was encountered: {:02X} at address: {:06X}", - o, addr - ), - CpuError::UnhandledInstructionError(o, addr) => write!( - f, - "An unhandled instruction was encountered: {:02X} at address: {:06X}", - o, addr - ), - CpuError::InstructionDecodeError(addr) => write!( - f, - "An error occurred during instruction decode at address: {:06X}", - addr - ), - CpuError::ExecutionError(addr, err) => { - write!(f, "An execution error occurred at: {:06X} Message: {}", addr, err) - } - CpuError::CpuHaltedError(addr) => { - write!(f, "The CPU was halted at address: {:06X}.", addr) - } - CpuError::ExceptionError(exception) => { - write!(f, "The CPU threw an exception: {:?}", exception) - } - } - } -} - -// Internal Emulator interrupt service events. These are returned to the machine when -// the internal service interrupt is called to request an emulator action that cannot -// be handled by the CPU alone. -#[derive(Copy, Clone, Debug)] -pub enum ServiceEvent { - TriggerPITLogging, -} - #[derive(Copy, Clone, Debug)] pub enum CallStackEntry { Call { @@ -400,32 +363,6 @@ pub enum Register { IP, }*/ -#[derive(Copy, Clone)] -pub enum OperandType { - Immediate8(u8), - Immediate16(u16), - Immediate8s(i8), - Relative8(i8), - Relative16(i16), - Offset8(u16), - Offset16(u16), - Register8(Register8), - Register16(Register16), - AddressingMode(AddressingMode), - FarAddress(u16, u16), - NoOperand, - InvalidOperand, -} - -#[derive(Copy, Clone, Debug)] -pub enum Displacement { - NoDisp, - Pending8, - Pending16, - Disp8(i8), - Disp16(i16), -} - #[derive(Copy, Clone, Default, Debug)] pub enum DmaState { #[default] @@ -438,23 +375,6 @@ pub enum DmaState { //DmaWait(u8) } -impl Displacement { - pub fn get_i16(&self) -> i16 { - match self { - Displacement::Disp8(disp) => *disp as i16, - Displacement::Disp16(disp) => *disp, - _ => 0, - } - } - pub fn get_u16(&self) -> u16 { - match self { - Displacement::Disp8(disp) => (*disp as i16) as u16, - Displacement::Disp16(disp) => *disp as u16, - _ => 0, - } - } -} - #[derive(Default, Debug)] pub enum RepType { #[default] @@ -465,25 +385,6 @@ pub enum RepType { MulDiv, } -#[derive(Copy, Clone, Default, Debug)] -pub enum Segment { - None, - ES, - #[default] - CS, - SS, - DS, -} - -#[derive(Copy, Clone, Default, PartialEq)] -pub enum OperandSize { - #[default] - NoOperand, - NoSize, - Operand8, - Operand16, -} - #[allow(dead_code)] #[derive(Copy, Clone, Debug, PartialEq)] pub enum InterruptType { @@ -565,58 +466,6 @@ impl Default for TransferSize { } } -#[derive(Copy, Clone, Debug)] -pub enum CpuAddress { - Flat(u32), - Segmented(u16, u16), - Offset(u16), -} - -impl Default for CpuAddress { - fn default() -> CpuAddress { - CpuAddress::Segmented(0, 0) - } -} - -impl From for u32 { - fn from(cpu_address: CpuAddress) -> Self { - match cpu_address { - CpuAddress::Flat(a) => a, - CpuAddress::Segmented(s, o) => Intel808x::calc_linear_address(s, o), - CpuAddress::Offset(a) => a as Self, - } - } -} - -impl Display for CpuAddress { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - CpuAddress::Flat(a) => write!(f, "{:05X}", a), - CpuAddress::Segmented(s, o) => write!(f, "{:04X}:{:04X}", s, o), - CpuAddress::Offset(a) => write!(f, "{:04X}", a), - } - } -} - -impl PartialEq for CpuAddress { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (CpuAddress::Flat(a), CpuAddress::Flat(b)) => a == b, - (CpuAddress::Flat(a), CpuAddress::Segmented(s, o)) => { - let b = Intel808x::calc_linear_address(*s, *o); - *a == b - } - (CpuAddress::Flat(_a), CpuAddress::Offset(_b)) => false, - (CpuAddress::Segmented(s, o), CpuAddress::Flat(b)) => { - let a = Intel808x::calc_linear_address(*s, *o); - a == *b - } - (CpuAddress::Segmented(s1, o1), CpuAddress::Segmented(s2, o2)) => *s1 == *s2 && *o1 == *o2, - _ => false, - } - } -} - #[derive(Default)] pub struct I8288 { // Command bus @@ -873,37 +722,6 @@ pub enum RegisterType { } */ -#[derive(Debug)] -pub enum StepResult { - Normal, - // If a call occurred, we return the address of the next instruction after the call - // so that we can step over the call in the debugger. - Call(CpuAddress), - // If we are in a REP prefixed string operation, we return the address of the next instruction - // so that we can step over the string operation. - Rep(CpuAddress), - BreakpointHit, - StepOverHit, - ProgramEnd, -} - -#[derive(Debug, PartialEq)] -pub enum ExecutionResult { - Okay, - OkayJump, - OkayRep, - //UnsupportedOpcode(u8), // All opcodes implemented. - ExecutionError(String), - ExceptionError(CpuException), - Halt, -} - -impl Default for ExecutionResult { - fn default() -> ExecutionResult { - ExecutionResult::Okay - } -} - /// The 8088 has a 7-cycle bus access time. 3 of these cycles can be pipelined during the previous /// bus cycle. These cycles can alternatively be considered an 'address cycle'. /// Tr: The cycle on which the EU or prefetcher requests a bus cycle @@ -993,15 +811,6 @@ impl Display for BusStatus { } } -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub enum QueueOp { - #[default] - Idle, - First, - Flush, - Subsequent, -} - impl Intel808x { pub fn new( cpu_type: CpuType, @@ -2121,7 +1930,7 @@ impl Intel808x { } #[cfg(feature = "cpu_validator")] - pub fn get_validator(&mut self) -> &Option> { + pub fn get_validator(&self) -> &Option> { &self.validator } diff --git a/core/src/cpu_808x/modrm.rs b/core/src/cpu_808x/modrm.rs index 2aa7dbec..da026197 100644 --- a/core/src/cpu_808x/modrm.rs +++ b/core/src/cpu_808x/modrm.rs @@ -31,7 +31,8 @@ */ use crate::{ bytequeue::*, - cpu_808x::{addressing::AddressingMode, *}, + cpu_808x::*, + cpu_common::{AddressingMode, Displacement}, }; pub const MODRM_REG_MASK: u8 = 0b00_111_000; diff --git a/core/src/cpu_808x/stack.rs b/core/src/cpu_808x/stack.rs index 63092ab9..d3500296 100644 --- a/core/src/cpu_808x/stack.rs +++ b/core/src/cpu_808x/stack.rs @@ -30,7 +30,10 @@ */ -use crate::cpu_808x::{biu::*, *}; +use crate::{ + cpu_808x::{biu::*, *}, + cpu_common::Segment, +}; impl Intel808x { pub fn push_u8(&mut self, data: u8, flag: ReadWriteFlag) { diff --git a/core/src/cpu_808x/step.rs b/core/src/cpu_808x/step.rs index f2e2a12e..614245c4 100644 --- a/core/src/cpu_808x/step.rs +++ b/core/src/cpu_808x/step.rs @@ -30,7 +30,11 @@ */ -use crate::cpu_808x::*; +use crate::{ + cpu_808x::{decode::DECODE, *}, + cpu_common::{CpuAddress, CpuError, CpuException, ExecutionResult, StepResult}, + gdr, +}; impl Intel808x { /// Run a single instruction. @@ -570,7 +574,7 @@ impl Intel808x { &self.instr_slice, v_flags, self.peek_fetch as u16, - self.i.gdr().has_modrm(), + gdr!(self.i).has_modrm(), 0, &vregs, &self.cycle_states, diff --git a/core/src/cpu_808x/string.rs b/core/src/cpu_808x/string.rs index ed67cc48..881a48eb 100644 --- a/core/src/cpu_808x/string.rs +++ b/core/src/cpu_808x/string.rs @@ -30,7 +30,10 @@ */ -use crate::{cpu_808x::*, cpu_common::alu::AluSub}; +use crate::{ + cpu_808x::*, + cpu_common::{alu::AluSub, Mnemonic, Segment}, +}; impl Intel808x { pub fn string_op(&mut self, opcode: Mnemonic, segment_override: Option) { diff --git a/core/src/cpu_common/addressing.rs b/core/src/cpu_common/addressing.rs new file mode 100644 index 00000000..d4dd9403 --- /dev/null +++ b/core/src/cpu_common/addressing.rs @@ -0,0 +1,239 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_common::addressing.rs + + This module defines addressing modes shared between CPU types. + +*/ + +use crate::cpu_vx0::NecVx0; +use std::{fmt, fmt::Display}; + +#[derive(Copy, Clone, Debug)] +pub enum Displacement { + NoDisp, + Pending8, + Pending16, + Disp8(i8), + Disp16(i16), +} + +#[derive(Copy, Clone, Debug)] +pub enum AddressingMode { + BxSi, + BxDi, + BpSi, + BpDi, + Si, + Di, + Disp16(Displacement), + Bx, + BxSiDisp8(Displacement), + BxDiDisp8(Displacement), + BpSiDisp8(Displacement), + BpDiDisp8(Displacement), + SiDisp8(Displacement), + DiDisp8(Displacement), + BpDisp8(Displacement), + BxDisp8(Displacement), + BxSiDisp16(Displacement), + BxDiDisp16(Displacement), + BpSiDisp16(Displacement), + BpDiDisp16(Displacement), + SiDisp16(Displacement), + DiDisp16(Displacement), + BpDisp16(Displacement), + BxDisp16(Displacement), + RegisterMode, +} + +pub(crate) struct SignedHex(pub T); +pub(crate) struct WithPlusSign(pub T); +pub(crate) struct WithSign(pub T); + +impl Displacement { + pub fn get_i16(&self) -> i16 { + match self { + Displacement::Disp8(disp) => *disp as i16, + Displacement::Disp16(disp) => *disp, + _ => 0, + } + } + pub fn get_u16(&self) -> u16 { + match self { + Displacement::Disp8(disp) => (*disp as i16) as u16, + Displacement::Disp16(disp) => *disp as u16, + _ => 0, + } + } +} + +impl fmt::Display for Displacement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Displacement::Pending8 | Displacement::Pending16 | Displacement::NoDisp => { + write!(f, "Invalid Displacement") + } + Displacement::Disp8(i) => { + write!(f, "{:X}h", i) + } + Displacement::Disp16(i) => { + write!(f, "{:X}h", i) + } + } + } +} + +impl fmt::Display for SignedHex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + Displacement::Pending8 | Displacement::Pending16 | Displacement::NoDisp => { + write!(f, "Invalid Displacement") + } + Displacement::Disp8(i) => { + if *i < 0 { + write!(f, "{:X}h", !i.wrapping_sub(1)) + } + else { + write!(f, "{:X}h", i) + } + } + Displacement::Disp16(i) => { + if *i < 0 { + write!(f, "{:X}h", !i.wrapping_sub(1)) + } + else { + write!(f, "{:X}h", i) + } + } + } + } +} + +impl Display for WithPlusSign { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + Displacement::Pending8 | Displacement::Pending16 | Displacement::NoDisp => { + write!(f, "Invalid Displacement") + } + Displacement::Disp8(i) => { + if *i < 0 { + write!(f, "-{}", SignedHex(self.0)) + } + else { + write!(f, "+{}", SignedHex(self.0)) + } + } + Displacement::Disp16(i) => { + if *i < 0 { + write!(f, "-{}", SignedHex(self.0)) + } + else { + write!(f, "+{}", SignedHex(self.0)) + } + } + } + } +} + +impl Display for WithSign { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + Displacement::Pending8 | Displacement::Pending16 | Displacement::NoDisp => { + write!(f, "Invalid Displacement") + } + Displacement::Disp8(i) => { + if *i < 0 { + write!(f, "-{}", SignedHex(self.0)) + } + else { + write!(f, "{}", SignedHex(self.0)) + } + } + Displacement::Disp16(i) => { + if *i < 0 { + write!(f, "-{}", SignedHex(self.0)) + } + else { + write!(f, "{}", SignedHex(self.0)) + } + } + } + } +} + +#[derive(Copy, Clone, Debug)] +pub enum CpuAddress { + Flat(u32), + Segmented(u16, u16), + Offset(u16), +} + +impl Default for CpuAddress { + fn default() -> CpuAddress { + CpuAddress::Segmented(0, 0) + } +} + +impl From for u32 { + fn from(cpu_address: CpuAddress) -> Self { + match cpu_address { + CpuAddress::Flat(a) => a, + CpuAddress::Segmented(s, o) => NecVx0::calc_linear_address(s, o), + CpuAddress::Offset(a) => a as Self, + } + } +} + +impl Display for CpuAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CpuAddress::Flat(a) => write!(f, "{:05X}", a), + CpuAddress::Segmented(s, o) => write!(f, "{:04X}:{:04X}", s, o), + CpuAddress::Offset(a) => write!(f, "{:04X}", a), + } + } +} + +impl PartialEq for CpuAddress { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (CpuAddress::Flat(a), CpuAddress::Flat(b)) => a == b, + (CpuAddress::Flat(a), CpuAddress::Segmented(s, o)) => { + let b = NecVx0::calc_linear_address(*s, *o); + *a == b + } + (CpuAddress::Flat(_a), CpuAddress::Offset(_b)) => false, + (CpuAddress::Segmented(s, o), CpuAddress::Flat(b)) => { + let a = NecVx0::calc_linear_address(*s, *o); + a == *b + } + (CpuAddress::Segmented(s1, o1), CpuAddress::Segmented(s2, o2)) => *s1 == *s2 && *o1 == *o2, + _ => false, + } + } +} diff --git a/core/src/cpu_common/builder.rs b/core/src/cpu_common/builder.rs index 0936cca1..15a0c846 100644 --- a/core/src/cpu_common/builder.rs +++ b/core/src/cpu_common/builder.rs @@ -33,6 +33,7 @@ use crate::{ cpu_808x::Intel808x, cpu_common::{CpuDispatch, CpuSubType, CpuType, TraceMode}, cpu_validator::{ValidatorMode, ValidatorType}, + cpu_vx0::NecVx0, tracelogger::TraceLogger, }; use anyhow::{bail, Result}; @@ -63,9 +64,9 @@ impl CpuBuilder { if let Some(cpu_type) = self.cpu_type { match cpu_type { - CpuType::Intel808x => { + CpuType::Intel8088 => { let mut cpu = Intel808x::new( - CpuType::Intel808x, + CpuType::Intel8088, CpuSubType::Intel8088, self.trace_mode, self.trace_logger.take().unwrap_or_default(), @@ -80,6 +81,22 @@ impl CpuBuilder { ); return Ok(cpu.into()); } + CpuType::NecV20 => { + let mut cpu = NecVx0::new( + CpuType::NecV20, + self.trace_mode, + self.trace_logger.take().unwrap_or_default(), + #[cfg(feature = "cpu_validator")] + self.validator_type, + #[cfg(feature = "cpu_validator")] + self.validator_logger.take().unwrap_or_default(), + #[cfg(feature = "cpu_validator")] + self.validator_mode.take().unwrap_or_default(), + #[cfg(feature = "cpu_validator")] + self.validator_baud.take().unwrap_or_default(), + ); + return Ok(cpu.into()); + } _ => { bail!("Unimplemented CPU type: {:?}", cpu_type); } diff --git a/core/src/cpu_common/error.rs b/core/src/cpu_common/error.rs new file mode 100644 index 00000000..51f7fd9f --- /dev/null +++ b/core/src/cpu_common/error.rs @@ -0,0 +1,75 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_common::error.rs + + This module defines a common error type for CPUs. + +*/ + +use crate::cpu_common::CpuException; +use std::{error::Error, fmt, fmt::Display}; + +#[derive(Debug)] +pub enum CpuError { + InvalidInstructionError(u8, u32), + UnhandledInstructionError(u8, u32), + InstructionDecodeError(u32), + ExecutionError(u32, String), + CpuHaltedError(u32), + ExceptionError(CpuException), +} +impl Error for CpuError {} +impl Display for CpuError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &*self { + CpuError::InvalidInstructionError(o, addr) => write!( + f, + "An invalid instruction was encountered: {:02X} at address: {:06X}", + o, addr + ), + CpuError::UnhandledInstructionError(o, addr) => write!( + f, + "An unhandled instruction was encountered: {:02X} at address: {:06X}", + o, addr + ), + CpuError::InstructionDecodeError(addr) => write!( + f, + "An error occurred during instruction decode at address: {:06X}", + addr + ), + CpuError::ExecutionError(addr, err) => { + write!(f, "An execution error occurred at: {:06X} Message: {}", addr, err) + } + CpuError::CpuHaltedError(addr) => { + write!(f, "The CPU was halted at address: {:06X}.", addr) + } + CpuError::ExceptionError(exception) => { + write!(f, "The CPU threw an exception: {:?}", exception) + } + } + } +} diff --git a/core/src/cpu_common/instruction.rs b/core/src/cpu_common/instruction.rs new file mode 100644 index 00000000..69bd42cc --- /dev/null +++ b/core/src/cpu_common/instruction.rs @@ -0,0 +1,684 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_common::builder.rs + + Implements the common Instruction type. + +*/ + +use std::{ + fmt, + fmt::{Display, Formatter, Result as fmtResult}, +}; + +use super::{addressing::WithPlusSign, mnemonic::mnemonic_to_str}; +use crate::{ + cpu_808x::{OPCODE_PREFIX_LOCK, OPCODE_PREFIX_REP1, OPCODE_PREFIX_REP2}, + cpu_common::{operands::OperandSize, AddressingMode, Mnemonic, OperandType, Register16, Register8, Segment}, + syntax_token::{SyntaxFormatType, SyntaxToken, SyntaxTokenVec, SyntaxTokenize}, +}; + +#[derive(Copy, Clone)] +pub enum OperandSelect { + FirstOperand, + SecondOperand, +} + +#[derive(Clone)] +pub struct Instruction { + pub decode_idx: usize, + pub opcode: u8, + pub prefixes: u32, + pub address: u32, + pub size: u32, + pub mnemonic: Mnemonic, + pub segment_override: Option, + pub operand1_type: OperandType, + pub operand1_size: OperandSize, + pub operand2_type: OperandType, + pub operand2_size: OperandSize, +} + +impl Default for Instruction { + fn default() -> Self { + Self { + decode_idx: 0, + opcode: 0, + prefixes: 0, + address: 0, + size: 1, + mnemonic: Mnemonic::NOP, + segment_override: None, + operand1_type: OperandType::NoOperand, + operand1_size: OperandSize::NoOperand, + operand2_type: OperandType::NoOperand, + operand2_size: OperandSize::NoOperand, + } + } +} + +impl Display for Instruction { + fn fmt(&self, f: &mut Formatter<'_>) -> fmtResult { + let mut instruction_string = String::new(); + + // Stick segment override prefix on certain opcodes (string ops) + let sego_prefix = override_prefix_to_string(self); + if let Some(so) = sego_prefix { + instruction_string.push_str(&so); + instruction_string.push_str(" "); + } + + // Add other prefixes (rep(x), lock, etc) + let prefix = prefix_to_string(self); + let mnemonic = mnemonic_to_str(self.mnemonic).to_string().to_lowercase(); + + if let Some(p) = prefix { + instruction_string.push_str(&p); + instruction_string.push_str(" "); + } + + instruction_string.push_str(&mnemonic); + + // Dont sign-extend 8-bit port addresses. + let op_size = match self.mnemonic { + Mnemonic::IN | Mnemonic::OUT => OperandSize::Operand8, + _ => self.operand1_size, + }; + + let op1 = operand_to_string(self, OperandSelect::FirstOperand, op_size); + if op1.len() > 0 { + instruction_string.push_str(" "); + instruction_string.push_str(&op1); + } + + let op2: String = operand_to_string(self, OperandSelect::SecondOperand, op_size); + if op2.len() > 0 { + instruction_string.push_str(", "); + instruction_string.push_str(&op2); + } + + write!(f, "{}", instruction_string) + } +} + +impl SyntaxTokenize for Instruction { + fn tokenize(&self) -> Vec { + // Dont sign-extend 8-bit port addresses. + let op_size = match self.mnemonic { + Mnemonic::IN | Mnemonic::OUT => OperandSize::Operand8, + _ => self.operand1_size, + }; + + let mut i_vec = SyntaxTokenVec(Vec::new()); + + // Stick segment override prefix on certain opcodes (string ops) + let sego_prefix = override_prefix_to_string(self); + if let Some(so) = sego_prefix { + i_vec.0.push(SyntaxToken::Prefix(so)); + i_vec.0.push(SyntaxToken::Formatter(SyntaxFormatType::Space)); + } + + let prefix = prefix_to_string(self); + if let Some(p) = prefix { + i_vec.0.push(SyntaxToken::Prefix(p)); + i_vec.0.push(SyntaxToken::Formatter(SyntaxFormatType::Space)); + } + + let mnemonic = mnemonic_to_str(self.mnemonic).to_string().to_lowercase(); + i_vec.0.push(SyntaxToken::Mnemonic(mnemonic)); + + let op1_vec = tokenize_operand(self, OperandSelect::FirstOperand, op_size); + i_vec.append(op1_vec, Some(SyntaxToken::Formatter(SyntaxFormatType::Space)), None); + + let op2_vec = tokenize_operand(self, OperandSelect::SecondOperand, op_size); + + if !op2_vec.is_empty() { + i_vec.0.push(SyntaxToken::Comma); + i_vec.append(op2_vec, Some(SyntaxToken::Formatter(SyntaxFormatType::Space)), None); + } + + i_vec.0 + } +} + +struct Imm8Extend(u8); +struct Imm8sExtend(i8); +struct Rel8Extend(i8); + +impl fmt::UpperHex for Imm8sExtend { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let prefix = if f.alternate() { "0x" } else { "" }; + // Check if the value is negative (the top bit of an i8 is set) + if self.0 < 0 { + // If it's negative, sign-extend with FF + let bare_hex = format!("FF{:2X}", self.0 as u8); + f.pad_integral(true, prefix, &bare_hex) + } + else { + // If it's positive or zero, simply show the original byte + let bare_hex = format!("{:X}", self.0 as u8); + f.pad_integral(true, prefix, &bare_hex) + } + } +} + +impl fmt::UpperHex for Imm8Extend { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let prefix = if f.alternate() { "0x" } else { "" }; + // Check if the highest bit (bit 7) is set + if self.0 & 0x80 != 0 { + // If it's set, sign-extend with FF + let bare_hex = format!("FF{:02X}", self.0); + f.pad_integral(true, prefix, &bare_hex) + } + else { + // If it's not set, simply show the original byte + let bare_hex = format!("{:02X}", self.0); + f.pad_integral(true, prefix, &bare_hex) + } + } +} + +impl fmt::UpperHex for Rel8Extend { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let prefix = if f.alternate() { "0x" } else { "" }; + // Check if the value is negative (the top bit of an i8 is set) + if self.0 < 0 { + // If it's negative, sign-extend with FF + let bare_hex = format!("FF{:02X}", self.0 as u8); + f.pad_integral(true, prefix, &bare_hex) + } + else { + // If it's positive or zero, simply show the original byte + let bare_hex = format!("00{:02X}", self.0 as u8); + f.pad_integral(true, prefix, &bare_hex) + } + } +} + +fn operand_to_string(i: &Instruction, op: OperandSelect, lvalue: OperandSize) -> String { + let (op_type, op_size) = match op { + OperandSelect::FirstOperand => (i.operand1_type, i.operand1_size), + OperandSelect::SecondOperand => (i.operand2_type, i.operand2_size), + }; + + let instruction_string: String = match op_type { + OperandType::Immediate8(imm8) => { + if let OperandSize::Operand8 = lvalue { + format!("{:X}h", imm8) + } + else { + format!("{:X}h", Imm8Extend(imm8)) + } + } + OperandType::Immediate8s(imm8) => { + // imm8 is always sign-extended to 16 + format!("{:X}h", Imm8sExtend(imm8)) + } + OperandType::Immediate16(imm16) => { + format!("{:X}h", imm16) + } + OperandType::Relative8(rel8) => { + //format!("short {:04X}h", i.size as i16 + rel8 as i16) + format!("{:04X}h", i.size as i16 + rel8 as i16) + } + OperandType::Relative16(rel16) => { + //format!("short {:04X}h", i.size as i16 + rel16) + format!("{:04X}h", i.size as i16 + rel16) + } + OperandType::Offset8(offset8) => { + let segment: String = match i.segment_override { + Some(Segment::ES) => "es".to_string(), + Some(Segment::CS) => "cs".to_string(), + Some(Segment::SS) => "ss".to_string(), + _ => "ds".to_string(), + }; + format!("byte [{}:{:X}h]", segment, offset8) + } + OperandType::Offset16(offset16) => { + let segment: String = match i.segment_override { + Some(Segment::ES) => "es".to_string(), + Some(Segment::CS) => "cs".to_string(), + Some(Segment::SS) => "ss".to_string(), + _ => "ds".to_string(), + }; + format!("word [{}:{:X}h]", segment, offset16) + } + OperandType::Register8(reg8) => match reg8 { + Register8::AL => "al".to_string(), + Register8::CL => "cl".to_string(), + Register8::DL => "dl".to_string(), + Register8::BL => "bl".to_string(), + Register8::AH => "ah".to_string(), + Register8::CH => "ch".to_string(), + Register8::DH => "dh".to_string(), + Register8::BH => "bh".to_string(), + }, + OperandType::Register16(reg16) => match reg16 { + Register16::AX => "ax".to_string(), + Register16::CX => "cx".to_string(), + Register16::DX => "dx".to_string(), + Register16::BX => "bx".to_string(), + Register16::SP => "sp".to_string(), + Register16::BP => "bp".to_string(), + Register16::SI => "si".to_string(), + Register16::DI => "di".to_string(), + Register16::ES => "es".to_string(), + Register16::CS => "cs".to_string(), + Register16::SS => "ss".to_string(), + Register16::DS => "ds".to_string(), + _ => "".to_string(), + }, + OperandType::AddressingMode(addr_mode) => { + let mut ptr_prefix: String = match op_size { + OperandSize::Operand8 => "byte ".to_string(), + OperandSize::Operand16 => "word ".to_string(), + OperandSize::NoOperand => "*invalid ptr* ".to_string(), + OperandSize::NoSize => "".to_string(), + }; + // LEA uses addressing calculations but isn't actually a pointer + if let Mnemonic::LEA = i.mnemonic { + ptr_prefix = "".to_string() + } + // LES and LDS point to a DWORD address + if let Mnemonic::LES | Mnemonic::LDS = i.mnemonic { + ptr_prefix = "dword ".to_string() + } + + let mut segment1 = "ds".to_string(); + let mut segment2 = "ss".to_string(); + + // Handle segment override prefixes + match i.segment_override { + Some(Segment::ES) => { + segment1 = "es".to_string(); + segment2 = "es".to_string(); + } + Some(Segment::CS) => { + segment1 = "cs".to_string(); + segment2 = "cs".to_string(); + } + Some(Segment::SS) => { + segment1 = "ss".to_string(); + segment2 = "ss".to_string(); + } + Some(Segment::DS) => { + segment1 = "ds".to_string(); + segment2 = "ds".to_string(); + } + _ => {} + } + + match addr_mode { + AddressingMode::BxSi => format!("{}[{}:bx+si]", ptr_prefix, segment1), + AddressingMode::BxDi => format!("{}[{}:bx+di]", ptr_prefix, segment1), + AddressingMode::BpSi => format!("{}[{}:bp+si]", ptr_prefix, segment2), + AddressingMode::BpDi => format!("{}[{}:bp+di]", ptr_prefix, segment2), + AddressingMode::Si => format!("{}[{}:si]", ptr_prefix, segment1), + AddressingMode::Di => format!("{}[{}:di]", ptr_prefix, segment1), + AddressingMode::Disp16(disp) => format!("{}[{}:{}]", ptr_prefix, segment1, disp), + AddressingMode::Bx => format!("{}[{}:bx]", ptr_prefix, segment1), + AddressingMode::BxSiDisp8(disp) => { + format!("{}[{}:bx+si{}]", ptr_prefix, segment1, WithPlusSign(disp)) + } + AddressingMode::BxDiDisp8(disp) => { + format!("{}[{}:bx+di{}]", ptr_prefix, segment1, WithPlusSign(disp)) + } + AddressingMode::BpSiDisp8(disp) => { + format!("{}[{}:bp+si{}]", ptr_prefix, segment2, WithPlusSign(disp)) + } + AddressingMode::BpDiDisp8(disp) => { + format!("{}[{}:bp+di{}]", ptr_prefix, segment2, WithPlusSign(disp)) + } + AddressingMode::SiDisp8(disp) => { + format!("{}[{}:si{}]", ptr_prefix, segment1, WithPlusSign(disp)) + } + AddressingMode::DiDisp8(disp) => { + format!("{}[{}:di{}]", ptr_prefix, segment1, WithPlusSign(disp)) + } + AddressingMode::BpDisp8(disp) => { + format!("{}[{}:bp{}]", ptr_prefix, segment2, WithPlusSign(disp)) + } + AddressingMode::BxDisp8(disp) => { + format!("{}[{}:bx{}]", ptr_prefix, segment1, WithPlusSign(disp)) + } + AddressingMode::BxSiDisp16(disp) => { + format!("{}[{}:bx+si{}]", ptr_prefix, segment1, WithPlusSign(disp)) + } + AddressingMode::BxDiDisp16(disp) => { + format!("{}[{}:bx+di{}]", ptr_prefix, segment1, WithPlusSign(disp)) + } + AddressingMode::BpSiDisp16(disp) => { + format!("{}[{}:bp+si{}]", ptr_prefix, segment2, WithPlusSign(disp)) + } + AddressingMode::BpDiDisp16(disp) => { + format!("{}[{}:bp+di{}]", ptr_prefix, segment2, WithPlusSign(disp)) + } + AddressingMode::SiDisp16(disp) => { + format!("{}[{}:si{}]", ptr_prefix, segment1, WithPlusSign(disp)) + } + AddressingMode::DiDisp16(disp) => { + format!("{}[{}:di{}]", ptr_prefix, segment1, WithPlusSign(disp)) + } + AddressingMode::BpDisp16(disp) => { + format!("{}[{}:bp{}]", ptr_prefix, segment2, WithPlusSign(disp)) + } + AddressingMode::BxDisp16(disp) => { + format!("{}[{}:bx{}]", ptr_prefix, segment1, WithPlusSign(disp)) + } + AddressingMode::RegisterMode => "".to_string(), + } + } + /* + OperandType::NearAddress(offset) => { + format!("[{:#06X}]", offset) + } + */ + OperandType::FarAddress(segment, offset) => { + format!("{:04X}h:{:04X}h", segment, offset) + } + OperandType::NoOperand => "".to_string(), + _ => "".to_string(), + }; + + instruction_string +} + +fn tokenize_operand(i: &Instruction, op: OperandSelect, lvalue: OperandSize) -> Vec { + let (op_type, op_size) = match op { + OperandSelect::FirstOperand => (i.operand1_type, i.operand1_size), + OperandSelect::SecondOperand => (i.operand2_type, i.operand2_size), + }; + + let mut op_vec = Vec::new(); + + match op_type { + OperandType::Immediate8(imm8) => { + if let OperandSize::Operand8 = lvalue { + op_vec.push(SyntaxToken::HexValue(format!("{:X}h", imm8))); + } + else { + op_vec.push(SyntaxToken::HexValue(format!("{:X}h", Imm8Extend(imm8)))); + } + } + OperandType::Immediate8s(imm8s) => { + op_vec.push(SyntaxToken::HexValue(format!("{:X}h", Imm8sExtend(imm8s)))); + } + OperandType::Immediate16(imm16) => { + op_vec.push(SyntaxToken::HexValue(format!("{:X}h", imm16))); + } + OperandType::Relative8(rel8) => { + //op_vec.push(SyntaxToken::Text("short".to_string())); + //op_vec.push(SyntaxToken::Formatter(SyntaxFormatType::Space)); + op_vec.push(SyntaxToken::HexValue(format!("{:04X}h", i.size as i16 + rel8 as i16))); + } + OperandType::Relative16(rel16) => { + //op_vec.push(SyntaxToken::Text("short".to_string())); + //op_vec.push(SyntaxToken::Formatter(SyntaxFormatType::Space)); + op_vec.push(SyntaxToken::HexValue(format!("{:04X}h", i.size as i16 + rel16))); + } + OperandType::Offset8(offset8) => { + let segment: String = match i.segment_override { + Some(Segment::ES) => "es".to_string(), + Some(Segment::CS) => "cs".to_string(), + Some(Segment::SS) => "ss".to_string(), + _ => "ds".to_string(), + }; + op_vec.push(SyntaxToken::Text("byte".to_string())); + op_vec.push(SyntaxToken::Formatter(SyntaxFormatType::Space)); + op_vec.push(SyntaxToken::OpenBracket); + op_vec.push(SyntaxToken::Segment(segment)); + op_vec.push(SyntaxToken::Colon); + op_vec.push(SyntaxToken::HexValue(format!("{:X}h", offset8))); + op_vec.push(SyntaxToken::CloseBracket); + } + OperandType::Offset16(offset16) => { + let segment: String = match i.segment_override { + Some(Segment::ES) => "es".to_string(), + Some(Segment::CS) => "cs".to_string(), + Some(Segment::SS) => "ss".to_string(), + _ => "ds".to_string(), + }; + op_vec.push(SyntaxToken::Text("word".to_string())); + op_vec.push(SyntaxToken::Formatter(SyntaxFormatType::Space)); + op_vec.push(SyntaxToken::OpenBracket); + op_vec.push(SyntaxToken::Segment(segment)); + op_vec.push(SyntaxToken::Colon); + op_vec.push(SyntaxToken::HexValue(format!("{:X}h", offset16))); + op_vec.push(SyntaxToken::CloseBracket); + } + OperandType::Register8(reg8) => { + let reg = match reg8 { + Register8::AL => "al".to_string(), + Register8::CL => "cl".to_string(), + Register8::DL => "dl".to_string(), + Register8::BL => "bl".to_string(), + Register8::AH => "ah".to_string(), + Register8::CH => "ch".to_string(), + Register8::DH => "dh".to_string(), + Register8::BH => "bh".to_string(), + }; + op_vec.push(SyntaxToken::Register(reg)); + } + OperandType::Register16(reg16) => { + let reg = match reg16 { + Register16::AX => "ax".to_string(), + Register16::CX => "cx".to_string(), + Register16::DX => "dx".to_string(), + Register16::BX => "bx".to_string(), + Register16::SP => "sp".to_string(), + Register16::BP => "bp".to_string(), + Register16::SI => "si".to_string(), + Register16::DI => "di".to_string(), + Register16::ES => "es".to_string(), + Register16::CS => "cs".to_string(), + Register16::SS => "ss".to_string(), + Register16::DS => "ds".to_string(), + _ => "".to_string(), + }; + op_vec.push(SyntaxToken::Register(reg)); + } + OperandType::AddressingMode(addr_mode) => { + let mut ptr_prefix: Option = match op_size { + OperandSize::Operand8 => Some("byte".to_string()), + OperandSize::Operand16 => Some("word".to_string()), + OperandSize::NoOperand => Some("*invalid*".to_string()), + OperandSize::NoSize => None, + }; + // LEA uses addressing calculations but isn't actually a pointer + if let Mnemonic::LEA = i.mnemonic { + ptr_prefix = None + } + // LES and LDS point to a DWORD address + if let Mnemonic::LES | Mnemonic::LDS = i.mnemonic { + ptr_prefix = Some("dword".to_string()) + } + + // Add pointer prefix, if any + if let Some(prefix) = ptr_prefix { + op_vec.push(SyntaxToken::Text(prefix)); + op_vec.push(SyntaxToken::Formatter(SyntaxFormatType::Space)) + } + + let mut segment1 = "ds".to_string(); + let mut segment2 = "ss".to_string(); + + // Handle segment override prefixes + match i.segment_override { + Some(Segment::ES) => { + segment1 = "es".to_string(); + segment2 = "es".to_string(); + } + Some(Segment::CS) => { + segment1 = "cs".to_string(); + segment2 = "cs".to_string(); + } + Some(Segment::SS) => { + segment1 = "ss".to_string(); + segment2 = "ss".to_string(); + } + Some(Segment::DS) => { + segment1 = "ds".to_string(); + segment2 = "ds".to_string(); + } + _ => {} + } + + let segment1_token = SyntaxToken::Segment(segment1); + let segment2_token = SyntaxToken::Segment(segment2); + + let mut have_addr_mode = true; + + let (seg_token, disp_opt, ea_vec) = match addr_mode { + AddressingMode::BxSi => (segment1_token, None, ["bx", "si"]), + AddressingMode::BxDi => (segment1_token, None, ["bx", "di"]), + AddressingMode::BpSi => (segment2_token, None, ["bp", "si"]), + AddressingMode::BpDi => (segment2_token, None, ["bp", "di"]), + AddressingMode::Si => (segment1_token, None, ["si", ""]), + AddressingMode::Di => (segment1_token, None, ["di", ""]), + AddressingMode::Disp16(disp) => (segment1_token, Some(disp), ["", ""]), + AddressingMode::Bx => (segment1_token, None, ["bx", ""]), + AddressingMode::BxSiDisp8(disp) => (segment1_token, Some(disp), ["bx", "si"]), + AddressingMode::BxDiDisp8(disp) => (segment1_token, Some(disp), ["bx", "di"]), + AddressingMode::BpSiDisp8(disp) => (segment2_token, Some(disp), ["bp", "si"]), + AddressingMode::BpDiDisp8(disp) => (segment2_token, Some(disp), ["bp", "di"]), + AddressingMode::SiDisp8(disp) => (segment1_token, Some(disp), ["si", ""]), + AddressingMode::DiDisp8(disp) => (segment1_token, Some(disp), ["di", ""]), + AddressingMode::BpDisp8(disp) => (segment2_token, Some(disp), ["bp", ""]), + AddressingMode::BxDisp8(disp) => (segment1_token, Some(disp), ["bx", ""]), + AddressingMode::BxSiDisp16(disp) => (segment1_token, Some(disp), ["bx", "si"]), + AddressingMode::BxDiDisp16(disp) => (segment1_token, Some(disp), ["bx", "di"]), + AddressingMode::BpSiDisp16(disp) => (segment2_token, Some(disp), ["bp", "si"]), + AddressingMode::BpDiDisp16(disp) => (segment2_token, Some(disp), ["bp", "di"]), + AddressingMode::SiDisp16(disp) => (segment1_token, Some(disp), ["si", ""]), + AddressingMode::DiDisp16(disp) => (segment1_token, Some(disp), ["di", ""]), + AddressingMode::BpDisp16(disp) => (segment2_token, Some(disp), ["bp", ""]), + AddressingMode::BxDisp16(disp) => (segment1_token, Some(disp), ["bx", ""]), + AddressingMode::RegisterMode => { + have_addr_mode = false; + (segment1_token, None, ["", ""]) + } + }; + + if have_addr_mode { + op_vec.push(SyntaxToken::OpenBracket); + op_vec.push(seg_token); + op_vec.push(SyntaxToken::Colon); + + if ea_vec[0].len() > 0 { + // Have first component of ea + op_vec.push(SyntaxToken::Register(ea_vec[0].to_string())); + } + else if let Some(disp) = disp_opt { + // Displacement by itself + op_vec.push(SyntaxToken::Displacement(format!("{}", disp))); + } + + if ea_vec[1].len() > 0 { + // Have second component of ea + op_vec.push(SyntaxToken::PlusSign); + op_vec.push(SyntaxToken::Register(ea_vec[1].to_string())); + } + + if ea_vec[0].len() > 0 { + // Have at least one ea component. Add +displacement if present. + if let Some(disp) = disp_opt { + // TODO: Generate +/- as tokens for displacement? + op_vec.push(SyntaxToken::Displacement(format!("{}", WithPlusSign(disp)))); + } + } + + op_vec.push(SyntaxToken::CloseBracket); + } + } + /* + OperandType::NearAddress(offset) => { + + op_vec.push(SyntaxToken::OpenBracket); + op_vec.push(SyntaxToken::HexValue(format!("{:04X}h", offset))); + op_vec.push(SyntaxToken::CloseBracket); + } + */ + OperandType::FarAddress(segment, offset) => { + op_vec.push(SyntaxToken::HexValue(format!("{:04X}h", segment))); + op_vec.push(SyntaxToken::Colon); + op_vec.push(SyntaxToken::HexValue(format!("{:04X}h", offset))); + } + _ => {} + }; + + op_vec +} + +fn override_prefix_to_string(i: &Instruction) -> Option { + if i.segment_override.is_some() { + match i.opcode { + 0xA4 | 0xA5 | 0xAA | 0xAB | 0xAC | 0xAD | 0xA6 | 0xA7 | 0xAE | 0xAF => { + let segment: String = match i.segment_override { + Some(Segment::ES) => "es".to_string(), + Some(Segment::CS) => "cs".to_string(), + Some(Segment::SS) => "ss".to_string(), + _ => "ds".to_string(), + }; + Some(segment) + } + _ => None, + } + } + else { + // No override + None + } +} + +fn prefix_to_string(i: &Instruction) -> Option { + // Handle REPx prefixes + // TODO: IS F2 valid on 6C, 6D, etc? + + if i.prefixes & OPCODE_PREFIX_LOCK != 0 { + Some("lock".to_string()) + } + else if i.prefixes & OPCODE_PREFIX_REP1 != 0 { + match i.opcode { + 0xF6 | 0xF7 => None, // Don't show REP prefix on div. + 0xA4 | 0xA5 | 0xAA | 0xAB | 0xAC | 0xAD => Some("rep".to_string()), + 0xA6 | 0xA7 | 0xAE | 0xAF => Some("repne".to_string()), + _ => None, + } + } + else if i.prefixes & OPCODE_PREFIX_REP2 != 0 { + match i.opcode { + 0xF6 | 0xF7 => None, // Don't show REP prefix on div. + 0xA4 | 0xA5 | 0xAA | 0xAB | 0xAC | 0xAD => Some("rep".to_string()), + 0xA6 | 0xA7 | 0xAE | 0xAF => Some("repe".to_string()), + _ => None, + } + } + else { + None + } +} diff --git a/core/src/cpu_common/mnemonic.rs b/core/src/cpu_common/mnemonic.rs new file mode 100644 index 00000000..a4432622 --- /dev/null +++ b/core/src/cpu_common/mnemonic.rs @@ -0,0 +1,282 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_common::mnemonic.rs + + Defines mnemonic enum. + +*/ + +use std::fmt; + +#[allow(dead_code)] +#[derive(PartialEq, Copy, Clone, Debug)] +pub enum Mnemonic { + InvalidOpcode, + NoOpcode, + Group, + Prefix, + NOP, + AAA, + AAD, + AAM, + AAS, + ADC, + ADD, + AND, + CALL, + CALLF, + CBW, + CLC, + CLD, + CLI, + CMC, + CMP, + CMPSB, + CMPSW, + CWD, + DAA, + DAS, + DEC, + DIV, + ESC, + WAIT, + HLT, + IDIV, + IMUL, + IN, + INC, + INT, + INT3, + INTO, + IRET, + JB, + JBE, + JCXZ, + JL, + JLE, + JMP, + JMPF, + JNB, + JNBE, + JNL, + JNLE, + JNO, + JNP, + JNS, + JNZ, + JO, + JP, + JS, + JZ, + LAHF, + LDS, + LEA, + LES, + LOCK, + LODSB, + LODSW, + LOOP, + LOOPNE, + LOOPE, + MOV, + MOVSB, + MOVSW, + MUL, + NEG, + NOT, + OR, + OUT, + POP, + POPF, + PUSH, + PUSHF, + RCL, + RCR, + REP, + REPNE, + REPE, + RETF, + RETN, + ROL, + ROR, + SAHF, + SALC, + SAR, + SBB, + SCASB, + SCASW, + SETMO, + SETMOC, + SHL, + SHR, + STC, + STD, + STI, + STOSB, + STOSW, + SUB, + TEST, + XCHG, + XLAT, + XOR, + // 186 Instructions + PUSHA, + POPA, + BOUND, + INS, + OUTS, +} + +impl Default for Mnemonic { + fn default() -> Self { + Mnemonic::InvalidOpcode + } +} + +// TODO: Is this any faster than just using derive Debug? +pub(crate) fn mnemonic_to_str(op: Mnemonic) -> &'static str { + match op { + Mnemonic::NOP => "NOP", + Mnemonic::AAA => "AAA", + Mnemonic::AAD => "AAD", + Mnemonic::AAM => "AAM", + Mnemonic::AAS => "AAS", + Mnemonic::ADC => "ADC", + Mnemonic::ADD => "ADD", + Mnemonic::AND => "AND", + Mnemonic::CALL => "CALL", + Mnemonic::CALLF => "CALLF", + Mnemonic::CBW => "CBW", + Mnemonic::CLC => "CLC", + Mnemonic::CLD => "CLD", + Mnemonic::CLI => "CLI", + Mnemonic::CMC => "CMC", + Mnemonic::CMP => "CMP", + Mnemonic::CMPSB => "CMPSB", + Mnemonic::CMPSW => "CMPSW", + Mnemonic::CWD => "CWD", + Mnemonic::DAA => "DAA", + Mnemonic::DAS => "DAS", + Mnemonic::DEC => "DEC", + Mnemonic::DIV => "DIV", + Mnemonic::ESC => "ESC", + Mnemonic::WAIT => "WAIT", + Mnemonic::HLT => "HLT", + Mnemonic::IDIV => "IDIV", + Mnemonic::IMUL => "IMUL", + Mnemonic::IN => "IN", + Mnemonic::INC => "INC", + Mnemonic::INT => "INT", + Mnemonic::INT3 => "INT3", + Mnemonic::INTO => "INTO", + Mnemonic::IRET => "IRET", + Mnemonic::JB => "JB", + Mnemonic::JBE => "JBE", + Mnemonic::JCXZ => "JCXZ", + Mnemonic::JL => "JL", + Mnemonic::JLE => "JLE", + Mnemonic::JMP => "JMP", + Mnemonic::JMPF => "JMPF", + Mnemonic::JNB => "JNB", + Mnemonic::JNBE => "JNBE", + Mnemonic::JNL => "JNL", + Mnemonic::JNLE => "JNLE", + Mnemonic::JNO => "JNO", + Mnemonic::JNP => "JNP", + Mnemonic::JNS => "JNS", + Mnemonic::JNZ => "JNZ", + Mnemonic::JO => "JO", + Mnemonic::JP => "JP", + Mnemonic::JS => "JS", + Mnemonic::JZ => "JZ", + Mnemonic::LAHF => "LAHF", + Mnemonic::LDS => "LDS", + Mnemonic::LEA => "LEA", + Mnemonic::LES => "LES", + Mnemonic::LOCK => "LOCK", + Mnemonic::LODSB => "LODSB", + Mnemonic::LODSW => "LODSW", + Mnemonic::LOOP => "LOOP", + Mnemonic::LOOPNE => "LOOPNE", + Mnemonic::LOOPE => "LOOPE", + Mnemonic::MOV => "MOV", + Mnemonic::MOVSB => "MOVSB", + Mnemonic::MOVSW => "MOVSW", + Mnemonic::MUL => "MUL", + Mnemonic::NEG => "NEG", + Mnemonic::NOT => "NOT", + Mnemonic::OR => "OR", + Mnemonic::OUT => "OUT", + Mnemonic::POP => "POP", + Mnemonic::POPF => "POPF", + Mnemonic::PUSH => "PUSH", + Mnemonic::PUSHF => "PUSHF", + Mnemonic::RCL => "RCL", + Mnemonic::RCR => "RCR", + Mnemonic::REP => "REP", + Mnemonic::REPNE => "REPNE", + Mnemonic::REPE => "REPE", + Mnemonic::RETF => "RETF", + Mnemonic::RETN => "RETN", + Mnemonic::ROL => "ROL", + Mnemonic::ROR => "ROR", + Mnemonic::SAHF => "SAHF", + Mnemonic::SALC => "SALC", + Mnemonic::SAR => "SAR", + Mnemonic::SBB => "SBB", + Mnemonic::SCASB => "SCASB", + Mnemonic::SCASW => "SCASW", + Mnemonic::SETMO => "SETMO", + Mnemonic::SETMOC => "SETMOC", + Mnemonic::SHL => "SHL", + Mnemonic::SHR => "SHR", + Mnemonic::STC => "STC", + Mnemonic::STD => "STD", + Mnemonic::STI => "STI", + Mnemonic::STOSB => "STOSB", + Mnemonic::STOSW => "STOSW", + Mnemonic::SUB => "SUB", + Mnemonic::TEST => "TEST", + Mnemonic::XCHG => "XCHG", + Mnemonic::XLAT => "XLAT", + Mnemonic::XOR => "XOR", + // 186 Instructions + Mnemonic::PUSHA => "PUSHA", + Mnemonic::POPA => "POPA", + Mnemonic::BOUND => "BOUND", + Mnemonic::INS => "INS", + Mnemonic::OUTS => "OUTS", + + _ => "INVALID", + } +} + +impl fmt::Display for Mnemonic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", mnemonic_to_str(*self)) + } +} diff --git a/core/src/cpu_common/mod.rs b/core/src/cpu_common/mod.rs index 272bebce..433e5076 100644 --- a/core/src/cpu_common/mod.rs +++ b/core/src/cpu_common/mod.rs @@ -32,30 +32,54 @@ #![allow(dead_code)] +pub mod addressing; pub mod alu; pub mod builder; +pub mod error; +pub mod instruction; +pub mod mnemonic; +pub mod operands; + +use enum_dispatch::enum_dispatch; +use serde::Deserialize; +use std::str::FromStr; + +pub use addressing::{AddressingMode, CpuAddress, Displacement}; +pub use error::CpuError; +pub use instruction::Instruction; +pub use mnemonic::Mnemonic; +pub use operands::OperandType; use crate::{ breakpoints::{BreakPointType, StopWatchData}, - bus::{BusInterface, ClockFactor}, + bus::BusInterface, bytequeue::ByteQueue, - cpu_808x::{ - mnemonic::Mnemonic, - CpuAddress, - CpuError, - Intel808x, - OperandSize, - OperandType, - Segment, - ServiceEvent, - StepResult, - }, + cpu_808x::Intel808x, cpu_validator::{CycleState, VRegisters}, - syntax_token::SyntaxToken, + cpu_vx0::NecVx0, + syntax_token::{SyntaxToken, SyntaxTokenize}, }; -use enum_dispatch::enum_dispatch; -use serde::Deserialize; -use std::str::FromStr; + +#[cfg(feature = "cpu_validator")] +use crate::cpu_validator::CpuValidator; + +#[derive(Debug, Default, PartialEq)] +pub enum ExecutionResult { + #[default] + Okay, + OkayJump, + OkayRep, + //UnsupportedOpcode(u8), // All opcodes implemented. + ExecutionError(String), + ExceptionError(CpuException), + Halt, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum CpuException { + NoException, + DivideError, +} #[derive(Copy, Clone, PartialEq)] pub enum Register8 { @@ -87,6 +111,16 @@ pub enum Register16 { InvalidRegister, } +#[derive(Copy, Clone, Default, Debug)] +pub enum Segment { + None, + ES, + #[default] + CS, + SS, + DS, +} + #[derive(Default, Debug, Clone)] pub struct CpuStringState { pub ah: String, @@ -129,18 +163,23 @@ pub struct CpuStringState { #[derive(Copy, Clone, Debug, Deserialize, PartialEq)] pub enum CpuType { - Intel808x, + Intel8088, + Intel8086, + NecV20, + NecV30, } impl CpuType { pub fn decode(&self, bytes: &mut impl ByteQueue, peek: bool) -> Result> { match self { - CpuType::Intel808x => Intel808x::decode(bytes, peek), + CpuType::Intel8088 | CpuType::Intel8086 => Intel808x::decode(bytes, peek), + CpuType::NecV20 | CpuType::NecV30 => NecVx0::decode(bytes, peek), } } pub fn tokenize_instruction(&self, instruction: &Instruction) -> Vec { match self { - CpuType::Intel808x => Intel808x::tokenize_instruction(instruction), + CpuType::Intel8088 | CpuType::Intel8086 => instruction.tokenize(), + CpuType::NecV20 | CpuType::NecV30 => instruction.tokenize(), } } } @@ -193,7 +232,7 @@ impl Default for TraceMode { impl Default for CpuType { fn default() -> Self { - CpuType::Intel808x + CpuType::Intel8088 } } @@ -210,46 +249,45 @@ pub enum CpuOption { EnableServiceInterrupt(bool), } -pub fn calc_linear_address(segment: u16, offset: u16) -> u32 { - (((segment as u32) << 4) + offset as u32) & 0xFFFFFu32 +#[derive(Debug)] +pub enum StepResult { + Normal, + // If a call occurred, we return the address of the next instruction after the call + // so that we can step over the call in the debugger. + Call(CpuAddress), + // If we are in a REP prefixed string operation, we return the address of the next instruction + // so that we can step over the string operation. + Rep(CpuAddress), + BreakpointHit, + StepOverHit, + ProgramEnd, } -#[derive(Clone)] -pub struct Instruction { - pub decode_idx: usize, - pub opcode: u8, - pub prefixes: u32, - pub address: u32, - pub size: u32, - pub mnemonic: Mnemonic, - pub segment_override: Option, - pub operand1_type: OperandType, - pub operand1_size: OperandSize, - pub operand2_type: OperandType, - pub operand2_size: OperandSize, +// Internal Emulator interrupt service events. These are returned to the machine when +// the internal service interrupt is called to request an emulator action that cannot +// be handled by the CPU alone. +#[derive(Copy, Clone, Debug)] +pub enum ServiceEvent { + TriggerPITLogging, } -impl Default for Instruction { - fn default() -> Self { - Self { - decode_idx: 0, - opcode: 0, - prefixes: 0, - address: 0, - size: 1, - mnemonic: Mnemonic::NOP, - segment_override: None, - operand1_type: OperandType::NoOperand, - operand1_size: OperandSize::NoOperand, - operand2_type: OperandType::NoOperand, - operand2_size: OperandSize::NoOperand, - } - } +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub enum QueueOp { + #[default] + Idle, + First, + Flush, + Subsequent, +} + +pub fn calc_linear_address(segment: u16, offset: u16) -> u32 { + (((segment as u32) << 4) + offset as u32) & 0xFFFFFu32 } #[enum_dispatch] pub enum CpuDispatch { Intel808x, + NecVx0, } #[enum_dispatch(CpuDispatch)] @@ -269,6 +307,8 @@ pub trait Cpu { fn get_ip(&mut self) -> u16; fn get_register16(&self, reg: Register16) -> u16; fn set_register16(&mut self, reg: Register16, value: u16); + fn get_register8(&self, reg: Register8) -> u8; + fn set_register8(&mut self, reg: Register8, value: u8); fn get_flags(&self) -> u16; fn set_flags(&mut self, flags: u16); fn get_cycle_ct(&self) -> (u64, u64); @@ -283,8 +323,7 @@ pub trait Cpu { fn get_cycle_states(&self) -> &Vec; fn get_cycle_trace(&self) -> &Vec; fn get_cycle_trace_tokens(&self) -> &Vec>; - #[cfg(feature = "cpu_validator")] - fn get_vregisters(&self) -> VRegisters; + fn get_string_state(&self) -> CpuStringState; // Eval @@ -310,4 +349,17 @@ pub trait Cpu { fn cycle_table_header(&self) -> Vec; fn emit_header(&mut self); fn trace_flush(&mut self); + + // Validation methods + #[cfg(feature = "cpu_validator")] + fn get_vregisters(&self) -> VRegisters; + #[cfg(feature = "cpu_validator")] + fn get_validator(&self) -> &Option>; + #[cfg(feature = "cpu_validator")] + fn get_validator_mut(&mut self) -> &mut Option>; + fn randomize_seed(&mut self, seed: u64); + fn randomize_mem(&mut self); + fn randomize_regs(&mut self); + fn random_grp_instruction(&mut self, opcode: u8, extension_list: &[u8]); + fn random_inst_from_opcodes(&mut self, opcode_list: &[u8]); } diff --git a/core/src/cpu_common/operands.rs b/core/src/cpu_common/operands.rs new file mode 100644 index 00000000..34d848d3 --- /dev/null +++ b/core/src/cpu_common/operands.rs @@ -0,0 +1,59 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_common::operands.rs + + This module defines operand types common between CPU types. + +*/ + +use crate::cpu_common::{AddressingMode, Register16, Register8}; + +#[derive(Copy, Clone)] +pub enum OperandType { + Immediate8(u8), + Immediate16(u16), + Immediate8s(i8), + Relative8(i8), + Relative16(i16), + Offset8(u16), + Offset16(u16), + Register8(Register8), + Register16(Register16), + AddressingMode(AddressingMode), + FarAddress(u16, u16), + NoOperand, + InvalidOperand, +} + +#[derive(Copy, Clone, Default, PartialEq)] +pub enum OperandSize { + #[default] + NoOperand, + NoSize, + Operand8, + Operand16, +} diff --git a/core/src/cpu_validator.rs b/core/src/cpu_validator.rs index 76c9b057..e444646a 100644 --- a/core/src/cpu_validator.rs +++ b/core/src/cpu_validator.rs @@ -46,7 +46,7 @@ use serde::{ Serializer, }; -use crate::cpu_808x::QueueOp; +use crate::cpu_common::QueueOp; pub const VAL_NO_READS: u8 = 0b0000_0001; // Don't validate read op data pub const VAL_NO_WRITES: u8 = 0b0000_0010; // Don't validate write op data diff --git a/core/src/cpu_vx0/addressing.rs b/core/src/cpu_vx0/addressing.rs new file mode 100644 index 00000000..28d4a532 --- /dev/null +++ b/core/src/cpu_vx0/addressing.rs @@ -0,0 +1,437 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::addressing.rs + + Implements addressing mode and operand loading routines. + +*/ + +use crate::{ + cpu_common::{operands::OperandSize, AddressingMode, Displacement, OperandType, Segment}, + cpu_vx0::{biu::*, decode::DECODE, *}, +}; + +#[derive(Copy, Clone, Debug)] +pub enum FarPtr { + Offset, + Segment, +} + +#[rustfmt::skip] +impl NecVx0 { + #[allow(dead_code)] + #[inline] + fn is_register_mode(mode: AddressingMode) -> bool { + matches!(mode, AddressingMode::RegisterMode) + } + + pub fn calc_linear_address(segment: u16, offset: u16) -> u32 { + (((segment as u32) << 4) + offset as u32) & 0xFFFFFu32 + } + + pub fn relative_offset_u16(base: u16, offset: i16) -> u16 { + base.wrapping_add(offset as u16) + } + + #[inline] + pub fn calc_linear_address_seg(&self, segment: Segment, offset: u16) -> u32 { + let segment_val: u16 = match segment { + Segment::None => 0, + Segment::ES => self.es, + Segment::CS => self.cs, + Segment::DS => self.ds, + Segment::SS => self.ss, + }; + (((segment_val as u32) << 4) + offset as u32) & 0xFFFFFu32 + } + + /// Calculate the Effective Address for the given AddressingMode enum + pub fn calc_effective_address( + &mut self, + mode: AddressingMode, + segment_override: Option, + ) -> (Segment, u16) { + // Addressing modes that reference BP use the stack segment instead of data segment + // unless a segment override is present. + + // Override default segments based on prefix + let segment_base_ds = segment_override.unwrap_or(Segment::DS); + let segment_base_ss = segment_override.unwrap_or(Segment::SS); + + let (seg, offset) = match mode { + // All of this relies on 2's compliment arithmetic for signed displacements + AddressingMode::BxSi => (segment_base_ds, self.b.x().wrapping_add(self.si)), + AddressingMode::BxDi => (segment_base_ds, self.b.x().wrapping_add(self.di)), + AddressingMode::BpSi => (segment_base_ss, self.bp.wrapping_add(self.si)), // BP -> SS default seg + AddressingMode::BpDi => (segment_base_ss, self.bp.wrapping_add(self.di)), // BP -> SS default seg + AddressingMode::Si => (segment_base_ds, self.si), + AddressingMode::Di => (segment_base_ds, self.di), + AddressingMode::Disp16(disp16) => (segment_base_ds, disp16.get_u16()), + AddressingMode::Bx => (segment_base_ds, self.b.x()), + + AddressingMode::BxSiDisp8(disp8) => (segment_base_ds, self.b.x().wrapping_add(self.si.wrapping_add(disp8.get_u16()))), + AddressingMode::BxDiDisp8(disp8) => (segment_base_ds, self.b.x().wrapping_add(self.di.wrapping_add(disp8.get_u16()))), + AddressingMode::BpSiDisp8(disp8) => (segment_base_ss, self.bp.wrapping_add(self.si.wrapping_add(disp8.get_u16()))), // BP -> SS default seg + AddressingMode::BpDiDisp8(disp8) => (segment_base_ss, self.bp.wrapping_add(self.di.wrapping_add(disp8.get_u16()))), // BP -> SS default seg + AddressingMode::SiDisp8(disp8) => (segment_base_ds, self.si.wrapping_add(disp8.get_u16())), + AddressingMode::DiDisp8(disp8) => (segment_base_ds, self.di.wrapping_add(disp8.get_u16())), + AddressingMode::BpDisp8(disp8) => (segment_base_ss, self.bp.wrapping_add(disp8.get_u16())), // BP -> SS default seg + AddressingMode::BxDisp8(disp8) => (segment_base_ds, self.b.x().wrapping_add(disp8.get_u16())), + + AddressingMode::BxSiDisp16(disp16) => (segment_base_ds, self.b.x().wrapping_add(self.si.wrapping_add(disp16.get_u16()))), + AddressingMode::BxDiDisp16(disp16) => (segment_base_ds, self.b.x().wrapping_add(self.di.wrapping_add(disp16.get_u16()))), + AddressingMode::BpSiDisp16(disp16) => (segment_base_ss, self.bp.wrapping_add(self.si.wrapping_add(disp16.get_u16()))), // BP -> SS default reg + AddressingMode::BpDiDisp16(disp16) => (segment_base_ss, self.bp.wrapping_add(self.di.wrapping_add(disp16.get_u16()))), // BP -> SS default reg + AddressingMode::SiDisp16(disp16) => (segment_base_ds, self.si.wrapping_add(disp16.get_u16())), + AddressingMode::DiDisp16(disp16) => (segment_base_ds, self.di.wrapping_add(disp16.get_u16())), + AddressingMode::BpDisp16(disp16) => (segment_base_ss, self.bp.wrapping_add(disp16.get_u16())), // BP -> SS default reg + AddressingMode::BxDisp16(disp16) => (segment_base_ds, self.b.x().wrapping_add(disp16.get_u16())), + + // The instruction decoder should convert ModRM operands that specify Registers to Register type operands, so + // in theory this shouldn't happen + AddressingMode::RegisterMode => panic!("Can't calculate EA for register") + }; + + self.last_ea = offset; // Save last EA to do voodoo when LEA is called with reg, reg operands + (seg, offset) + } + + pub fn load_effective_address(&mut self, operand: OperandType) -> Option { + if let OperandType::AddressingMode(mode) = operand { + let (_segment, offset) = + self.calc_effective_address(mode, None); + return Some(offset); + } + None + } + + /// Load the EA operand for the current instruction, if applicable + /// (not all instructions with a mod r/m will load, ie, write-only instructions) + pub fn load_operand(&mut self) { + if DECODE[self.i.decode_idx].gdr.loads_ea() { + // This instruction loads its EA operand. Load and save into OPR. + + let ea_mode: AddressingMode; + let ea_size; + if let OperandType::AddressingMode(mode) = self.i.operand1_type { + ea_size = self.i.operand1_size; + ea_mode = mode; + } + else if let OperandType::AddressingMode(mode) = self.i.operand2_type { + ea_size = self.i.operand2_size; + ea_mode = mode; + } + else { + return; + } + + let (segment, offset) = self.calc_effective_address(ea_mode, self.i.segment_override); + + self.trace_comment("EALOAD"); + + /* + // We can use the width bit (bit 0) of the opcode to determine size of operand when a modrm is present, + // but 8C and 8E are exceptions to the rule... + let wide = match self.i.opcode { + 0xC4 | 0x8C | 0x8E => true, + _ => self.i.opcode & 0x01 == 1 + }; + */ + + if ea_size == OperandSize::Operand16 { + // Width is word + assert!(ea_size == OperandSize::Operand16); + self.ea_opr = self.biu_read_u16(segment, offset, ReadWriteFlag::Normal); + } + else { + // Width is byte + assert!(ea_size == OperandSize::Operand8); + self.ea_opr = self.biu_read_u8(segment, offset) as u16; + } + self.cycles_i(2, &[0x1e2, MC_RTN]); // Return delay cycle from EALOAD + } + } + + /// Return the value of an 8-bit Operand + pub fn read_operand8( + &mut self, + operand: OperandType, + seg_override: Option, + ) -> Option { + // The operand enums may contain values peeked from instruction fetch. However, for accurate cycle + // timing, we have to fetch them again now. + + // Originally we would assert that the peeked operand values equal the fetched values, but this can + // fail with self-modifying code, such as the end credits of 8088MPH. + match operand { + OperandType::Immediate8(_imm8) => { + let byte = self.q_read_u8(QueueType::Subsequent, QueueReader::Eu); + Some(byte) + } + OperandType::Immediate8s(_imm8s) => { + let byte = self.q_read_i8(QueueType::Subsequent, QueueReader::Eu); + Some(byte as u8) + } + OperandType::Relative8(_rel8) => { + let byte = self.q_read_i8(QueueType::Subsequent, QueueReader::Eu); + Some(byte as u8) + } + OperandType::Offset8(_offset8) => { + let offset = self.q_read_u16(QueueType::Subsequent, QueueReader::Eu); + let segment = seg_override.unwrap_or(Segment::DS); + let byte = self.biu_read_u8(segment, offset); + Some(byte) + } + OperandType::Register8(reg8) => match reg8 { + Register8::AH => Some(self.a.h()), + Register8::AL => Some(self.a.l()), + Register8::BH => Some(self.b.h()), + Register8::BL => Some(self.b.l()), + Register8::CH => Some(self.c.h()), + Register8::CL => Some(self.c.l()), + Register8::DH => Some(self.d.h()), + Register8::DL => Some(self.d.l()), + } + OperandType::AddressingMode(_mode) => { + // EA operand was already fetched into ea_opr. Return masked byte. + if self.i.opcode & 0x01 != 0 { + panic!("Reading byte operand for word size instruction"); + } + Some((self.ea_opr & 0xFF) as u8) + } + _ => None, + } + } + + /// Return the value of a 16-bit Operand + pub fn read_operand16( + &mut self, + operand: OperandType, + seg_override: Option, + ) -> Option { + // The operand enums may contain values peeked from instruction fetch. However, for accurate cycle + // timing, we have to fetch them again now. + + // Originally we would assert that the peeked operand values equal the fetched values, but this can + // fail with self-modifying code, such as the end credits of 8088MPH. + match operand { + OperandType::Immediate16(_imm16) => { + let word = self.q_read_u16(QueueType::Subsequent, QueueReader::Eu); + Some(word) + } + OperandType::Relative16(_rel16) => { + let word = self.q_read_i16(QueueType::Subsequent, QueueReader::Eu); + Some(word as u16) + } + OperandType::Offset16(_offset16) => { + let offset = self.q_read_u16(QueueType::Subsequent, QueueReader::Eu); + let segment = seg_override.unwrap_or(Segment::DS); + let word = self.biu_read_u16(segment, offset, ReadWriteFlag::Normal); + + Some(word) + } + OperandType::Register16(reg16) => match reg16 { + Register16::AX => Some(self.a.x()), + Register16::CX => Some(self.c.x()), + Register16::DX => Some(self.d.x()), + Register16::BX => Some(self.b.x()), + Register16::SP => Some(self.sp), + Register16::BP => Some(self.bp), + Register16::SI => Some(self.si), + Register16::DI => Some(self.di), + Register16::ES => Some(self.es), + Register16::CS => Some(self.cs), + Register16::SS => Some(self.ss), + Register16::DS => Some(self.ds), + _ => panic!("read_operand16(): Invalid Register16 operand: {:?}", reg16), + }, + OperandType::AddressingMode(_mode) => { + // EA operand was already fetched into ea_opr. Return it. + Some(self.ea_opr) + } + _ => None, + } + } + + /// Load a far address operand from instruction queue and return the segment, offset tuple. + pub fn read_operand_faraddr(&mut self) -> (u16, u16) { + let o1 = self.biu_queue_read(QueueType::Subsequent, QueueReader::Eu); + let o2 = self.biu_queue_read(QueueType::Subsequent, QueueReader::Eu); + let s1 = self.biu_queue_read(QueueType::Subsequent, QueueReader::Eu); + let s2 = self.biu_queue_read(QueueType::Subsequent, QueueReader::Eu); + + ( + (s1 as u16) | (s2 as u16) << 8, + (o1 as u16) | (o2 as u16) << 8, + ) + } + + pub fn read_operand_farptr( + &mut self, + operand: OperandType, + seg_override: Option, + flag: ReadWriteFlag, + ) -> Option<(u16, u16)> { + match operand { + OperandType::AddressingMode(mode) => { + let offset = self.ea_opr; + let (segment, ea_offset) = self.calc_effective_address(mode, seg_override); + let segment = self.biu_read_u16(segment, ea_offset.wrapping_add(2), flag); + Some((segment, offset)) + } + OperandType::Register16(_) => { + // Illegal form of LES/LDS reg/reg uses the last calculated EA. + let segment_base_ds = self.i.segment_override.unwrap_or(Segment::DS); + let offset = + self.biu_read_u16(segment_base_ds, self.last_ea, ReadWriteFlag::Normal); + let segment = self.biu_read_u16( + segment_base_ds, + self.last_ea.wrapping_add(2), + ReadWriteFlag::Normal, + ); + Some((segment, offset)) + } + _ => None, + } + } + + pub fn read_operand_farptr2( + &mut self, + operand: OperandType, + seg_override: Option, + ptr: FarPtr, + flag: ReadWriteFlag, + ) -> Option { + match operand { + OperandType::AddressingMode(mode) => { + let (segment, offset) = self.calc_effective_address(mode, seg_override); + + match ptr { + FarPtr::Offset => Some(self.biu_read_u16(segment, offset, flag)), + FarPtr::Segment => { + Some(self.biu_read_u16(segment, offset.wrapping_add(2), flag)) + } + } + } + OperandType::Register16(_) => { + // Illegal form of LES/LDS reg/reg uses the last calculated EA. + let segment_base_ds = self.i.segment_override.unwrap_or(Segment::DS); + match ptr { + FarPtr::Offset => Some(0), + FarPtr::Segment => { + Some(self.biu_read_u16(segment_base_ds, self.last_ea.wrapping_add(2), flag)) + } + } + } + _ => None, + } + } + + /// Write an 8-bit value to the specified destination operand + pub fn write_operand8( + &mut self, + operand: OperandType, + seg_override: Option, + value: u8, + flag: ReadWriteFlag, + ) { + match operand { + OperandType::Offset8(_offset8) => { + let offset = self.q_read_u16(QueueType::Subsequent, QueueReader::Eu); + self.cycle(); + let segment = seg_override.unwrap_or(Segment::DS); + self.biu_write_u8(segment, offset, value, flag); + } + OperandType::Register8(reg8) => match reg8 { + Register8::AH => self.set_register8(Register8::AH, value), + Register8::AL => self.set_register8(Register8::AL, value), + Register8::BH => self.set_register8(Register8::BH, value), + Register8::BL => self.set_register8(Register8::BL, value), + Register8::CH => self.set_register8(Register8::CH, value), + Register8::CL => self.set_register8(Register8::CL, value), + Register8::DH => self.set_register8(Register8::DH, value), + Register8::DL => self.set_register8(Register8::DL, value), + }, + OperandType::AddressingMode(mode) => { + let (segment, offset) = self.calc_effective_address(mode, seg_override); + self.biu_write_u8(segment, offset, value, flag); + } + _ => {} + } + } + + pub fn write_operand16( + &mut self, + operand: OperandType, + seg_override: Option, + value: u16, + flag: ReadWriteFlag, + ) { + match operand { + OperandType::Offset16(_offset16) => { + let offset = self.q_read_u16(QueueType::Subsequent, QueueReader::Eu); + self.cycle(); + let segment = seg_override.unwrap_or(Segment::DS); + self.biu_write_u16(segment, offset, value, flag); + } + OperandType::Register16(reg16) => { + match reg16 { + Register16::AX => self.set_register16(Register16::AX, value), + Register16::CX => self.set_register16(Register16::CX, value), + Register16::DX => self.set_register16(Register16::DX, value), + Register16::BX => self.set_register16(Register16::BX, value), + Register16::SP => self.set_register16(Register16::SP, value), + Register16::BP => self.set_register16(Register16::BP, value), + Register16::SI => self.set_register16(Register16::SI, value), + Register16::DI => self.set_register16(Register16::DI, value), + Register16::ES => { + self.set_register16(Register16::ES, value); + //self.interrupt_inhibit = true; + }, + Register16::CS => { + self.set_register16(Register16::CS, value); + //self.interrupt_inhibit = true; + }, + Register16::SS => { + self.set_register16(Register16::SS, value); + //self.interrupt_inhibit = true; + } + Register16::DS => { + self.set_register16(Register16::DS, value); + //self.interrupt_inhibit = true; + }, + _ => panic!("read_operand16(): Invalid Register16 operand"), + } + } + OperandType::AddressingMode(mode) => { + let (segment, offset) = self.calc_effective_address(mode, seg_override); + self.biu_write_u16(segment, offset, value, flag); + } + _ => {} + } + } +} diff --git a/core/src/cpu_vx0/alu.rs b/core/src/cpu_vx0/alu.rs new file mode 100644 index 00000000..817b07d9 --- /dev/null +++ b/core/src/cpu_vx0/alu.rs @@ -0,0 +1,368 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::alu.rs + + Implements common ALU operations. + +*/ +#![allow(dead_code)] + +use crate::{ + cpu_common::{alu::*, Mnemonic}, + cpu_vx0::*, +}; + +/// The ALU operation specifier 'Xi' determines the ALU operation by decoding 5 bits from the group +/// decode rom, opcode, and optionally modrm. We don't bother decoding Xi. Instead, Xi is stored +/// in the precalculated decode table. +pub enum Xi { + ADD, + ADC, + OR, + SBB, + SUB, + CMP, + AND, + XOR, + ROL, + ROR, + RCL, + RCR, + SHL, + SHR, + SETMO, + SAR, + PASS, + DAA, + DAS, + AAA, + AAS, + INC, + DEC, + NOT, + NEG, + INC2, + DEC2, +} + +//use num_traits::PrimInt; + +impl NecVx0 { + #[inline(always)] + fn set_parity_flag_from_u8(&mut self, operand: u8) { + self.set_flag_state(Flag::Parity, PARITY_TABLE[operand as usize]); + } + + #[inline(always)] + fn set_parity_flag_from_u16(&mut self, operand: u16) { + self.set_flag_state(Flag::Parity, PARITY_TABLE[(operand & 0xFF) as usize]); + } + + /* + #[inline(always)] + fn set_parity_flag(&mut self, result: T) { + self.set_flag_state(Flag::Parity, PARITY_TABLE[result.to_usize().unwrap() & 0xFF]); + } + */ + + /* + #[inline(always)] + pub fn set_szp_flags_from_result(&mut self, result: T) { + + // Set Sign flag to state of Sign (HO) bit + self.set_flag_state(Flag::Sign, result & (T::one() << (std::mem::size_of::() - 1)) != T::zero()); + + // Set Zero flag if result is 0, clear it if not + self.set_flag_state(Flag::Zero, result == T::zero()); + + // Set Parity Flag + self.set_parity_flag(result); + } + */ + pub fn set_szp_flags_from_result_u8(&mut self, result: u8) { + // Set Sign flag to state of Sign (HO) bit + self.set_flag_state(Flag::Sign, result & 0x80 != 0); + + // Set Zero flag if result is 0, clear it if not + self.set_flag_state(Flag::Zero, result == 0); + + // Set Parity Flag + self.set_parity_flag_from_u8(result); + } + + pub fn set_szp_flags_from_result_u16(&mut self, result: u16) { + // Set Sign flag to state of Sign (HO) bit + self.set_flag_state(Flag::Sign, result & 0x8000 != 0); + + // Set Zero flag if result is 0, clear it if not + self.set_flag_state(Flag::Zero, result == 0); + + // Set Parity Flag + self.set_parity_flag_from_u16(result); + } + + /// Perform various 8-bit math operations + pub fn math_op8(&mut self, opcode: Mnemonic, operand1: u8, operand2: u8) -> u8 { + match opcode { + Mnemonic::ADD => { + let (result, carry, overflow, aux_carry) = operand1.alu_add(operand2); + self.set_flag_state(Flag::Carry, carry); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u8(result); + result + } + Mnemonic::ADC => { + let (result, carry, overflow, aux_carry) = operand1.alu_adc(operand2, self.get_flag(Flag::Carry)); + self.set_flag_state(Flag::Carry, carry); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u8(result); + result + } + Mnemonic::SUB => { + let (result, carry, overflow, aux_carry) = operand1.alu_sub(operand2); + self.set_flag_state(Flag::Carry, carry); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u8(result); + result + } + Mnemonic::SBB => { + // Get value of carry flag + let carry_in = self.get_flag(Flag::Carry); + // And pass it to SBB + //let (result, carry, overflow, aux_carry) = Cpu::sub_u8(operand1, operand2, carry_in ); + + let (result, carry, overflow, aux_carry) = operand1.alu_sbb(operand2, carry_in); + self.set_flag_state(Flag::Carry, carry); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u8(result); + result + } + Mnemonic::NEG => { + // Compute (0-operand) + // Flags: The CF flag set to 0 if the source operand is 0; otherwise it is set to 1. + // The OF, SF, ZF, AF, and PF flags are set according to the result. + let (result, _carry, overflow, aux_carry) = 0u8.alu_sub(operand1); + + self.set_flag_state(Flag::Carry, operand1 != 0); + // NEG Updates AF, SF, PF, ZF + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u8(result); + result + } + Mnemonic::INC => { + // INC acts like add xx, 1, however does not set carry flag + let (result, _carry, overflow, aux_carry) = operand1.alu_add(1); + // DO NOT set carry Flag + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u8(result); + result + } + Mnemonic::DEC => { + // DEC acts like sub xx, 1, however does not set carry flag + let (result, _carry, overflow, aux_carry) = operand1.alu_sub(1); + // DEC does NOT set carry Flag + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u8(result); + result + } + Mnemonic::OR => { + let result = operand1 | operand2; + // Clear carry, overflow + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.set_szp_flags_from_result_u8(result); + result + } + Mnemonic::AND => { + let result = operand1 & operand2; + // Clear carry, overflow + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.set_szp_flags_from_result_u8(result); + result + } + Mnemonic::TEST => { + let result = operand1 & operand2; + // Clear carry, overflow + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.set_szp_flags_from_result_u8(result); + // TEST does not modify operand1 + operand1 + } + Mnemonic::XOR => { + let result = operand1 ^ operand2; + // Clear carry, overflow + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.set_szp_flags_from_result_u8(result); + result + } + Mnemonic::NOT => { + // Flags: None + !operand1 + } + Mnemonic::CMP => { + // CMP behaves like SUB except we do not store the result + let (result, carry, overflow, aux_carry) = operand1.alu_sub(operand2); + self.set_flag_state(Flag::Carry, carry); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u8(result); + // Return the operand1 unchanged + operand1 + } + _ => panic!("cpu::math_op8(): Invalid opcode: {:?}", opcode), + } + } + + /// Perform various 16-bit math operations + pub fn math_op16(&mut self, opcode: Mnemonic, operand1: u16, operand2: u16) -> u16 { + match opcode { + Mnemonic::ADD => { + let (result, carry, overflow, aux_carry) = operand1.alu_add(operand2); + self.set_flag_state(Flag::Carry, carry); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u16(result); + result + } + Mnemonic::ADC => { + let (result, carry, overflow, aux_carry) = operand1.alu_adc(operand2, self.get_flag(Flag::Carry)); + self.set_flag_state(Flag::Carry, carry); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u16(result); + result + } + Mnemonic::SUB => { + //let (result, carry, overflow, aux_carry) = Cpu::sub_u16(operand1, operand2, false ); + let (result, carry, overflow, aux_carry) = operand1.alu_sub(operand2); + self.set_flag_state(Flag::Carry, carry); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u16(result); + result + } + Mnemonic::SBB => { + // Get value of carry flag + let carry_in = self.get_flag(Flag::Carry); + // And pass it to SBB + //let (result, carry, overflow, aux_carry) = Cpu::sub_u16(operand1, operand2, carry_in ); + let (result, carry, overflow, aux_carry) = operand1.alu_sbb(operand2, carry_in); + self.set_flag_state(Flag::Carry, carry); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u16(result); + result + } + Mnemonic::NEG => { + // Compute (0-operand) + // Flags: The CF flag set to 0 if the source operand is 0; otherwise it is set to 1. + // The OF, SF, ZF, AF, and PF flags are set according to the result. + let (result, _carry, overflow, aux_carry) = 0u16.alu_sub(operand1); + + self.set_flag_state(Flag::Carry, operand1 != 0); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u16(result); + result + } + Mnemonic::INC => { + // INC acts like add xx, 1, however does not set carry flag + let (result, _carry, overflow, aux_carry) = operand1.alu_add(1); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u16(result); + result + } + Mnemonic::DEC => { + // DEC acts like sub xx, 1, however does not set carry flag + let (result, _carry, overflow, aux_carry) = operand1.alu_sub(1); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u16(result); + result + } + Mnemonic::OR => { + let result = operand1 | operand2; + // Clear carry, overflow + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.set_szp_flags_from_result_u16(result); + result + } + Mnemonic::AND => { + let result = operand1 & operand2; + // Clear carry, overflow + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.set_szp_flags_from_result_u16(result); + result + } + Mnemonic::TEST => { + let result = operand1 & operand2; + // Clear carry, overflow + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.set_szp_flags_from_result_u16(result); + // Do not modify operand + operand1 + } + Mnemonic::XOR => { + let result = operand1 ^ operand2; + // Clear carry, overflow + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.set_szp_flags_from_result_u16(result); + result + } + Mnemonic::NOT => { + // Flags: None + !operand1 + } + Mnemonic::CMP => { + // CMP behaves like SUB except we do not store the result + let (result, carry, overflow, aux_carry) = operand1.alu_sub(operand2); + self.set_flag_state(Flag::Carry, carry); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u16(result); + // Return the operand1 unchanged + operand1 + } + _ => panic!("cpu::math_op16(): Invalid opcode: {:?}", opcode), + } + } +} diff --git a/core/src/cpu_vx0/bcd.rs b/core/src/cpu_vx0/bcd.rs new file mode 100644 index 00000000..1e5a1a05 --- /dev/null +++ b/core/src/cpu_vx0/bcd.rs @@ -0,0 +1,268 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::bcd.rs + + Implements BCD (Binary Coded Decimal) routines. + +*/ + +use crate::cpu_vx0::{muldiv::*, *}; + +impl NecVx0 { + /// Ascii Adjust after Addition + /// Flags: AuxCarry and Carry are set per operation. The OF, SF, ZF, and PF flags are undefined. + pub fn aaa(&mut self) { + self.cycles_i(6, &[0x148, 0x149, 0x14a, 0x14b, 0x14c, 0x14d]); + + let old_al = self.a.l(); + let new_al; + + if ((self.a.l() & 0x0F) > 9) || self.get_flag(Flag::AuxCarry) { + // Intel documentation shows AX := AX + 106 for AAA, but this does not lead to correct + // behavior if AL carries to AH. Mistake on intel's part(?) + self.set_register8(Register8::AH, self.a.h().wrapping_add(1)); + new_al = self.a.l().wrapping_add(6); + self.set_register8(Register8::AL, new_al & 0x0F); + self.set_flag(Flag::AuxCarry); + self.set_flag(Flag::Carry); + //self.cycle_i(0x14e); + } + else { + new_al = self.a.l(); + self.set_register8(Register8::AL, self.a.l() & 0x0F); + self.clear_flag(Flag::AuxCarry); + self.clear_flag(Flag::Carry); + self.cycle_i(MC_JUMP); + } + + // Handle undefined flag behavior. Determined by testing against real 8088. + self.clear_flag(Flag::Overflow); + self.clear_flag(Flag::Zero); + self.clear_flag(Flag::Sign); + if new_al == 0 { + self.set_flag(Flag::Zero); + } + if old_al >= 0x7A && old_al <= 0x7F { + self.set_flag(Flag::Overflow); + } + if old_al >= 0x7A && old_al <= 0xF9 { + self.set_flag(Flag::Sign); + } + + self.set_flag_state(Flag::Parity, PARITY_TABLE[new_al as usize]); + } + + /// Ascii Adjust after Subtraction + /// Flags: AuxCarry and Carry are set per operation. The OF, SF, ZF, and PF flags are undefined. + pub fn aas(&mut self) { + let old_al = self.a.l(); + let old_af = self.get_flag(Flag::AuxCarry); + let new_al; + + self.cycles_i(6, &[0x148, 0x149, 0x14a, 0x14b, MC_JUMP, 0x14d]); + if ((self.a.l() & 0x0F) > 9) || old_af { + // Intel documentation shows AX := AX - 6 for AAS, but the microcode only reads AL not AX + // before calling XI. Mistake on intel's part(?) + new_al = self.a.l().wrapping_sub(6); + self.set_register8(Register8::AH, self.a.h().wrapping_sub(1)); + self.set_register8(Register8::AL, new_al & 0x0F); + self.set_flag(Flag::AuxCarry); + self.set_flag(Flag::Carry); + //self.cycle_i(0x14e); + } + else { + new_al = self.a.l(); + self.set_register8(Register8::AL, self.a.l() & 0x0F); + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::AuxCarry); + self.cycle_i(MC_JUMP); + } + + // Handle undefined flag behavior. Determined by testing against real 8088. + self.clear_flag(Flag::Overflow); + self.clear_flag(Flag::Zero); + self.clear_flag(Flag::Sign); + if new_al == 0 { + self.set_flag(Flag::Zero); + } + if old_af && old_al >= 0x80 && old_al <= 0x85 { + self.set_flag(Flag::Overflow); + } + if !old_af && old_al >= 0x80 { + self.set_flag(Flag::Sign); + } + if old_af && ((old_al <= 0x05) || (old_al >= 0x86)) { + self.set_flag(Flag::Sign); + } + + self.set_flag_state(Flag::Parity, PARITY_TABLE[new_al as usize]); + } + + /// Ascii adjust before Division + /// Flags: The SF, ZF, and PF flags are set according to the resulting binary value in the AL register + pub fn aad(&mut self, imm8: u8) { + self.cycles_i(3, &[0x170, 0x171, MC_JUMP]); + let product_native = (self.a.h() as u16).wrapping_mul(imm8 as u16) as u8; + let (_, product) = 0u8.corx(self, self.a.h() as u16, imm8 as u16, false); + assert_eq!((product as u8), product_native); + + self.set_register8(Register8::AL, self.a.l().wrapping_add(product as u8)); + self.set_register8(Register8::AH, 0); + + self.cycles_i(2, &[0x172, 0x173]); + + // Other sources set flags from AX register. Intel's documentation specifies AL + self.set_szp_flags_from_result_u8(self.a.l()); + } + + /// DAA — Decimal Adjust AL after Addition + /// Flags: The SF, ZF, and PF flags are set according to the result. OF is undefined. + /// See https://www.righto.com/2023/01/understanding-x86s-decimal-adjust-after.html for + /// clarification on intel's pseudocode for this function. + pub fn daa(&mut self) { + let old_cf = self.get_flag(Flag::Carry); + let old_af = self.get_flag(Flag::AuxCarry); + let old_al = self.a.l(); + + self.clear_flag(Flag::Carry); + + // DAA on the 8088 has different behavior from the pseudocode when AF==1. This was validated against hardware. + // It is probably something you'd only discover from fuzzing. + let al_check = match old_af { + true => 0x9F, + false => 0x99, + }; + + //log::debug!(" >>>> daa: af: {} cf: {} of: {}", old_af, old_cf, self.get_flag(Flag::Overflow)); + + // Handle undefined overflow flag behavior. Observed from testing against real cpu. + self.clear_flag(Flag::Overflow); + if old_cf { + if self.a.l() >= 0x1a && self.a.l() <= 0x7F { + self.set_flag(Flag::Overflow); + } + } + else if self.a.l() >= 0x7a && self.a.l() <= 0x7F { + self.set_flag(Flag::Overflow); + } + + if (self.a.l() & 0x0F) > 9 || self.get_flag(Flag::AuxCarry) { + self.set_register8(Register8::AL, self.a.l().wrapping_add(6)); + self.set_flag(Flag::AuxCarry); + } + else { + self.clear_flag(Flag::AuxCarry); + } + + if (old_al > al_check) || old_cf { + self.set_register8(Register8::AL, self.a.l().wrapping_add(0x60)); + self.set_flag(Flag::Carry); + } + else { + self.clear_flag(Flag::Carry); + } + + self.set_szp_flags_from_result_u8(self.a.l()); + } + + /// DAS — Decimal Adjust AL after Subtraction + /// Flags: The SF, ZF, and PF flags are set according to the result. + pub fn das(&mut self) { + let old_al = self.a.l(); + let old_af = self.get_flag(Flag::AuxCarry); + let old_cf = self.get_flag(Flag::Carry); + + let al_check = match old_af { + true => 0x9F, + false => 0x99, + }; + + // Handle undefined overflow flag behavior. Observed from testing against real cpu. + self.clear_flag(Flag::Overflow); + + match (old_af, old_cf) { + (false, false) => match self.a.l() { + 0x9A..=0xDF => self.set_flag(Flag::Overflow), + _ => {} + }, + (true, false) => match self.a.l() { + 0x80..=0x85 | 0xA0..=0xE5 => self.set_flag(Flag::Overflow), + _ => {} + }, + (false, true) => match self.a.l() { + 0x80..=0xDF => self.set_flag(Flag::Overflow), + _ => {} + }, + (true, true) => match self.a.l() { + 0x80..=0xE5 => self.set_flag(Flag::Overflow), + _ => {} + }, + } + + self.clear_flag(Flag::Carry); + if (self.a.l() & 0x0F) > 9 || self.get_flag(Flag::AuxCarry) { + self.set_register8(Register8::AL, self.a.l().wrapping_sub(6)); + self.set_flag(Flag::AuxCarry); + } + else { + self.clear_flag(Flag::AuxCarry); + } + + if (old_al > al_check) || old_cf { + self.set_register8(Register8::AL, self.a.l().wrapping_sub(0x60)); + self.set_flag(Flag::Carry); + } + else { + self.clear_flag(Flag::Carry); + } + + self.set_szp_flags_from_result_u8(self.a.l()); + } + + /// AAM - Ascii adjust AX After multiply + /// Flags: The SF, ZF, and PF flags are set according to the resulting binary value in the AL register + /// As AAM is implemented via CORD, it can throw an exception. This is indicated by a return value + /// of false. + pub fn aam(&mut self, imm8: u8) -> bool { + self.cycles_i(3, &[0x175, 0x176, MC_JUMP]); + // 176: A->tmpc | UNC CORD + // Jump delay + + match 0u8.cord(self, 0, imm8 as u16, self.a.l() as u16) { + Ok((quotient, remainder, _)) => { + // 177: | COM1 tmpc + self.set_register8(Register8::AH, !(quotient as u8)); + self.set_register8(Register8::AL, remainder as u8); + self.cycle_i(0x177); + // Other sources set flags from AX register. Intel's documentation specifies AL + self.set_szp_flags_from_result_u8(self.a.l()); + true + } + Err(_) => false, + } + } +} diff --git a/core/src/cpu_vx0/bitwise.rs b/core/src/cpu_vx0/bitwise.rs new file mode 100644 index 00000000..062bc96a --- /dev/null +++ b/core/src/cpu_vx0/bitwise.rs @@ -0,0 +1,631 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::bitwise.rs + + Implement bitwise operations (Shifts, rotations) + +*/ + +use crate::{cpu_common::Mnemonic, cpu_vx0::*}; + +impl NecVx0 { + pub(crate) fn shl_u8_with_carry(mut byte: u8, mut count: u8) -> (u8, bool) { + let mut carry = false; + while count > 0 { + carry = byte & 0x80 != 0; + byte <<= 1; + count -= 1; + } + (byte, carry) + } + + pub(crate) fn shl_u16_with_carry(mut word: u16, mut count: u8) -> (u16, bool) { + let mut carry = false; + while count > 0 { + carry = word & 0x8000 != 0; + word <<= 1; + count -= 1; + } + (word, carry) + } + + pub(crate) fn shr_u8_with_carry(mut byte: u8, mut count: u8) -> (u8, bool) { + let mut carry = false; + while count > 0 { + carry = byte & 0x01 != 0; + byte >>= 1; + count -= 1; + } + (byte, carry) + } + + pub(crate) fn shr_u16_with_carry(mut word: u16, mut count: u8) -> (u16, bool) { + let mut carry = false; + while count > 0 { + carry = word & 0x0001 != 0; + word >>= 1; + count -= 1; + } + (word, carry) + } + + pub(crate) fn rcr_u8_with_carry(mut byte: u8, mut count: u8, carry_flag: bool) -> (u8, bool) { + let mut saved_carry = carry_flag; + let mut new_carry; + + while count > 0 { + new_carry = byte & 0x01 != 0; + byte >>= 1; + if saved_carry { + byte |= 0x80; + } + saved_carry = new_carry; + count -= 1; + } + + (byte, saved_carry) + } + + pub(crate) fn rcr_u16_with_carry(mut word: u16, mut count: u8, carry_flag: bool) -> (u16, bool) { + let mut saved_carry = carry_flag; + let mut new_carry; + + while count > 0 { + new_carry = word & 0x0001 != 0; + word >>= 1; + if saved_carry { + word |= 0x8000; + } + saved_carry = new_carry; + count -= 1; + } + + (word, saved_carry) + } + + pub(crate) fn rcl_u8_with_carry(mut byte: u8, mut count: u8, carry_flag: bool) -> (u8, bool) { + let mut saved_carry = carry_flag; + let mut new_carry; + + while count > 0 { + new_carry = byte & 0x80 != 0; + byte <<= 1; + if saved_carry { + byte |= 0x01; + } + saved_carry = new_carry; + count -= 1; + } + + (byte, saved_carry) + } + + pub(crate) fn rcl_u16_with_carry(mut word: u16, mut count: u8, carry_flag: bool) -> (u16, bool) { + let mut saved_carry = carry_flag; + let mut new_carry; + + while count > 0 { + new_carry = word & 0x8000 != 0; + word <<= 1; + if saved_carry { + word |= 0x0001; + } + saved_carry = new_carry; + count -= 1; + } + + (word, saved_carry) + } + + pub(crate) fn ror_u8_with_carry(mut byte: u8, mut count: u8) -> (u8, bool) { + let mut carry = false; + while count > 0 { + carry = byte & 0x01 != 0; + byte >>= 1; + if carry { + byte |= 0x80; + } + count -= 1; + } + (byte, carry) + } + + pub(crate) fn ror_u16_with_carry(mut word: u16, mut count: u8) -> (u16, bool) { + let mut carry = false; + while count > 0 { + carry = word & 0x0001 != 0; + word >>= 1; + if carry { + word |= 0x8000; + } + count -= 1; + } + (word, carry) + } + + pub(crate) fn rol_u8_with_carry(mut byte: u8, mut count: u8) -> (u8, bool) { + let mut carry = false; + while count > 0 { + carry = byte & 0x80 != 0; + byte <<= 1; + if carry { + byte |= 0x01; + } + count -= 1; + } + + (byte, carry) + } + + pub(crate) fn rol_u16_with_carry(mut word: u16, mut count: u8) -> (u16, bool) { + let mut carry = false; + while count > 0 { + carry = word & 0x8000 != 0; + word <<= 1; + if carry { + word |= 0x0001; + } + count -= 1; + } + + (word, carry) + } + + pub(crate) fn sar_u8_with_carry(mut byte: u8, mut count: u8) -> (u8, bool) { + let mut carry = false; + let ho_bit = byte & 0x80; + while count > 0 { + carry = byte & 0x01 != 0; + byte >>= 1; + byte |= ho_bit; + count -= 1; + } + (byte, carry) + } + + pub(crate) fn sar_u16_with_carry(mut word: u16, mut count: u8) -> (u16, bool) { + let mut carry = false; + let ho_bit = word & 0x8000; + while count > 0 { + carry = word & 0x0001 != 0; + word >>= 1; + word |= ho_bit; + count -= 1; + } + (word, carry) + } + + /// Perform various 8-bit binary shift operations + pub fn bitshift_op8(&mut self, opcode: Mnemonic, operand1: u8, operand2: u8) -> u8 { + // Operand2 will either be 1 or value of CL register on 8088 + if operand2 == 0 { + // Flags are not changed if shift amount is 0 + return operand1; + } + + let result: u8; + let carry: bool; + + /* + // All processors after 8086 mask the rotation count to 5 bits (31 maximum) + let rot_count = match self.cpu_type { + CpuType::Cpu8088 | CpuType::Cpu8086 => operand2, + _=> operand2 & 0x1F + }; + */ + + let rot_count = operand2; + + match opcode { + Mnemonic::ROL => { + (result, carry) = NecVx0::rol_u8_with_carry(operand1, rot_count); + self.set_flag_state(Flag::Carry, carry); + // Only set overflow on ROL of 1 + if rot_count == 1 { + // Set overflow to XOR of MSB and CF + self.set_flag_state(Flag::Overflow, ((result & 0x80) != 0) ^ carry); + } + } + Mnemonic::ROR => { + (result, carry) = NecVx0::ror_u8_with_carry(operand1, rot_count); + self.set_flag_state(Flag::Carry, carry); + // Only set overflow on ROR of 1 + if rot_count == 1 { + // Set overflow to XOR of two MS bits + self.set_flag_state(Flag::Overflow, ((result & 0x80) != 0) ^ ((result & 0x40) != 0)); + } + } + Mnemonic::RCL => { + // Rotate with Carry Left + // Flags: For left rotates, the OF flag is set to the exclusive OR of the CF bit (after the rotate) + // and the most-significant bit of the result. + let existing_carry = self.get_flag(Flag::Carry); + (result, carry) = NecVx0::rcl_u8_with_carry(operand1, rot_count, existing_carry); + self.set_flag_state(Flag::Carry, carry); + // Only set overflow on SHL of 1 + if rot_count == 1 { + // Set overflow to XOR of MSB and CF + self.set_flag_state(Flag::Overflow, ((result & 0x80) != 0) ^ carry); + } + } + Mnemonic::RCR => { + let existing_carry = self.get_flag(Flag::Carry); + // Only set overflow on SHL of 1 + if rot_count == 1 { + // Set overflow to XOR of MSB and CF + self.set_flag_state(Flag::Overflow, ((operand1 & 0x80) != 0) ^ existing_carry); + } + + (result, carry) = NecVx0::rcr_u8_with_carry(operand1, rot_count, existing_carry); + self.set_flag_state(Flag::Carry, carry); + } + Mnemonic::SETMO => { + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::AuxCarry); + self.clear_flag(Flag::Zero); + self.clear_flag(Flag::Overflow); + + self.set_flag(Flag::Parity); + self.set_flag(Flag::Sign); + + result = 0xFF; + } + Mnemonic::SETMOC => { + if self.c.l() != 0 { + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::AuxCarry); + self.clear_flag(Flag::Zero); + self.clear_flag(Flag::Overflow); + + self.set_flag(Flag::Parity); + self.set_flag(Flag::Sign); + result = 0xFF; + } + else { + result = operand1; + } + } + Mnemonic::SHL => { + (result, carry) = NecVx0::shl_u8_with_carry(operand1, operand2); + // Set state of Carry Flag + self.set_flag_state(Flag::Carry, carry); + + // Only set overflow on SHL of 1 + if operand2 == 1 { + // If the two highest order bits were different, then they will change on shift + // and overflow should be set + self.set_flag_state(Flag::Overflow, (operand1 & 0xC0 == 0x80) || (operand1 & 0xC0 == 0x40)); + } + + self.set_szp_flags_from_result_u8(result); + } + Mnemonic::SHR => { + (result, carry) = NecVx0::shr_u8_with_carry(operand1, operand2); + // Set state of Carry Flag + self.set_flag_state(Flag::Carry, carry); + + // Only set overflow on SHR of 1 + if operand2 == 1 { + // Only time SHR sets overflow is if HO was 1 and becomes 0, which it always will, + // so set overflow flag if it was set. + self.set_flag_state(Flag::Overflow, operand1 & 0x80 != 0); + } + self.set_szp_flags_from_result_u8(result); + } + Mnemonic::SAR => { + (result, carry) = NecVx0::sar_u8_with_carry(operand1, operand2); + // Set Carry Flag + self.set_flag_state(Flag::Carry, carry); + + // Clear overflow flag if shift count is 1 + // AoA 6.6.2.2 SAR + if operand2 == 1 { + self.clear_flag(Flag::Overflow); + } + self.set_szp_flags_from_result_u8(result); + } + _ => panic!("Invalid opcode provided to bitshift_op8()"), + } + + // Return result + result + } + + /// Peform various 16-bit binary shift operations + pub fn bitshift_op16(&mut self, opcode: Mnemonic, operand1: u16, operand2: u8) -> u16 { + // Operand2 will either be 1 or value of CL register on 8088 + if operand2 == 0 { + // Flags are not changed if shift amount is 0 + return operand1; + } + + let result: u16; + let carry: bool; + + /* + // All processors after 8086 mask the rotation count to 5 bits (31 maximum) + let rot_count = match self.cpu_type { + CpuType::Cpu8088 | CpuType::Cpu8086 => operand2, + _=> operand2 & 0x1F + }; + */ + + let rot_count = operand2; + + match opcode { + Mnemonic::ROL => { + // Rotate Left + // Flags: For left rotates, the OF flag is set to the exclusive OR of the CF bit (after the rotate) + // and the most-significant bit of the result. + (result, carry) = NecVx0::rol_u16_with_carry(operand1, rot_count); + self.set_flag_state(Flag::Carry, carry); + + // Overflow only defined for ROL of 1 + if rot_count == 1 { + // Set overflow to XOR of MSB and CF* + self.set_flag_state(Flag::Overflow, ((result & 0x8000) != 0) ^ carry); + } + } + Mnemonic::ROR => { + // Rotate Right + // Flags: For right rotates, the OF flag is set to the exclusive OR of the two most-significant bits of the result. + (result, carry) = NecVx0::ror_u16_with_carry(operand1, rot_count); + self.set_flag_state(Flag::Carry, carry); + + // Overflow only defined for ROR of 1 + if rot_count == 1 { + // Set overflow to XOR of two MS bits* + self.set_flag_state(Flag::Overflow, ((result & 0x8000) != 0) ^ ((result & 0x4000) != 0)); + } + } + Mnemonic::RCL => { + // Rotate with Carry Left + // Flags: For left rotates, the OF flag is set to the exclusive OR of the CF bit (after the rotate) + // and the most-significant bit of the result. + + let existing_carry = self.get_flag(Flag::Carry); + (result, carry) = NecVx0::rcl_u16_with_carry(operand1, rot_count, existing_carry); + self.set_flag_state(Flag::Carry, carry); + // Overflow only defined for RCL of 1 + if rot_count == 1 { + // Set overflow to XOR of MSB and CF* + self.set_flag_state(Flag::Overflow, ((result & 0x8000) != 0) ^ carry); + } + } + Mnemonic::RCR => { + // Rotate with Carry Right + // Flags: For right rotates, the OF flag is set to the exclusive OR of the two most-significant bits of the result. + + // Only set overflow on SHL of 1 + let existing_carry = self.get_flag(Flag::Carry); + + // Overflow only defined for RCL of 1 + if rot_count == 1 { + // Set overflow to XOR of MSB and CF* + self.set_flag_state(Flag::Overflow, ((operand1 & 0x8000) != 0) ^ existing_carry); + } + + (result, carry) = NecVx0::rcr_u16_with_carry(operand1, rot_count, existing_carry); + self.set_flag_state(Flag::Carry, carry); + + // The rcr instruction does not affect the zero, sign, parity, or auxiliary carry flags. + // AoA 6.6.3.2 + } + Mnemonic::SETMO => { + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::AuxCarry); + self.clear_flag(Flag::Zero); + self.clear_flag(Flag::Overflow); + + self.set_flag(Flag::Parity); + self.set_flag(Flag::Sign); + + result = 0xFFFF; + } + Mnemonic::SETMOC => { + if self.c.l() != 0 { + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::AuxCarry); + self.clear_flag(Flag::Zero); + self.clear_flag(Flag::Overflow); + + self.set_flag(Flag::Parity); + self.set_flag(Flag::Sign); + result = 0xFFFF; + } + else { + result = operand1; + } + } + Mnemonic::SHL => { + (result, carry) = NecVx0::shl_u16_with_carry(operand1, operand2); + // Set state of Carry Flag + self.set_flag_state(Flag::Carry, carry); + + // Only set overflow on SHL of 1 + if operand2 == 1 { + // If the two highest order bits were different, then they will change on shift + // and overflow should be set + self.set_flag_state( + Flag::Overflow, + (operand1 & 0xC000 == 0x8000) || (operand1 & 0xC000 == 0x4000), + ); + } + self.set_szp_flags_from_result_u16(result); + } + Mnemonic::SHR => { + (result, carry) = NecVx0::shr_u16_with_carry(operand1, operand2); + // Set state of Carry Flag + self.set_flag_state(Flag::Carry, carry); + + // Only set overflow on SHR of 1 + if operand2 == 1 { + // Only time SHR sets overflow is if HO was 1 and becomes 0, which it always will, + // so set overflow flag if it was set. + self.set_flag_state(Flag::Overflow, operand1 & 0x8000 != 0); + } + self.set_szp_flags_from_result_u16(result); + } + Mnemonic::SAR => { + (result, carry) = NecVx0::sar_u16_with_carry(operand1, operand2); + // Set Carry Flag + self.set_flag_state(Flag::Carry, carry); + + // Clear overflow flag if shift count is 1 + // AoA 6.6.2.2 SAR + if operand2 == 1 { + self.clear_flag(Flag::Overflow); + } + self.set_szp_flags_from_result_u16(result); + } + _ => panic!("Invalid opcode provided to bitshift_op16()"), + } + + // Return result + result + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_shr() { + let (result, carry) = Cpu::shr_u8_with_carry(0x80, 7); + assert_eq!(result, 1); + assert_eq!(carry, false); + let (result, carry) = Cpu::shr_u8_with_carry(0x04, 3); + assert_eq!(result, 0); + assert_eq!(carry, true); + let (result, carry) = Cpu::shr_u8_with_carry(0x04, 4); + assert_eq!(result, 0); + assert_eq!(carry, false); + + let (result16, carry) = Cpu::shr_u16_with_carry(0x0101, 1); + assert_eq!(result16, 0x0080); + assert_eq!(carry, true); + let (result16, carry) = Cpu::shr_u16_with_carry(0xFF00, 8); + assert_eq!(result16, 0x00FF); + assert_eq!(carry, false); + } + + #[test] + fn test_shl() { + let (result, carry) = Cpu::shl_u8_with_carry(0x80, 1); + assert_eq!(result, 0); + assert_eq!(carry, true); + let (result, carry) = Cpu::shl_u8_with_carry(0x01, 7); + assert_eq!(result, 0x80); + assert_eq!(carry, false); + + let (result, carry) = Cpu::shl_u16_with_carry(0x0080, 1); + assert_eq!(result, 0x0100); + assert_eq!(carry, false); + let (result, carry) = Cpu::shl_u16_with_carry(0xFF00, 8); + assert_eq!(result, 0x0000); + assert_eq!(carry, true); + } + + #[test] + fn test_sar_u8() { + let (result, carry) = Cpu::sar_u8_with_carry(0x80, 3); + assert_eq!(result, 0xF0); + assert_eq!(carry, false); + let (result, carry) = Cpu::sar_u8_with_carry(0x80, 8); + assert_eq!(result, 0xFF); + assert_eq!(carry, true); + + let (result, carry) = Cpu::sar_u16_with_carry(0x8000, 2); + assert_eq!(result, 0xE000); + assert_eq!(carry, false); + let (result, carry) = Cpu::sar_u16_with_carry(0x8001, 1); + assert_eq!(result, 0xC000); + assert_eq!(carry, true); + } + + #[test] + fn test_rcr() { + let (result, carry) = Cpu::rcr_u8_with_carry(0x01, 1, false); + assert_eq!(result, 0x00); + assert_eq!(carry, true); + let (result, carry) = Cpu::rcr_u8_with_carry(0x01, 3, false); + assert_eq!(result, 0x40); + assert_eq!(carry, false); + let (result, carry) = Cpu::rcr_u8_with_carry(0x00, 1, true); + assert_eq!(result, 0x80); + assert_eq!(carry, false); + + // Test overflow + let mut existing_carry = false; + let mut operand = 0x80; + let (result, carry) = Cpu::rcr_u8_with_carry(operand, 1, existing_carry); + let overflow = (operand & 0x80 == 0 && existing_carry) || (operand & 0x80 != 0 && !existing_carry); + assert_eq!(result, 0x40); + assert_eq!(carry, false); + assert_eq!(overflow, true); // Overflow should be set because HO bit changed from 1 to 0 + + operand = 0x04; + existing_carry = true; + + let (result, carry) = Cpu::rcr_u8_with_carry(operand, 1, existing_carry); + let overflow = (operand & 0x80 == 0 && existing_carry) || (operand & 0x80 != 0 && !existing_carry); + assert_eq!(result, 0x82); + assert_eq!(carry, false); + assert_eq!(overflow, true); // Overflow should be set because HO bit changed from 0 to 1 + } + + #[test] + fn test_rcl() { + let (result, carry) = Cpu::rcl_u8_with_carry(0x80, 1, false); + assert_eq!(result, 0x00); + assert_eq!(carry, true); + let (result, carry) = Cpu::rcl_u8_with_carry(0x80, 2, false); + assert_eq!(result, 0x01); + assert_eq!(carry, false); + + // RCL 17 should result in same value + let (result, carry) = Cpu::rcl_u16_with_carry(0xDEAD, 17, false); + assert_eq!(result, 0xDEAD); + assert_eq!(carry, false); + + let (result, carry) = Cpu::rcl_u16_with_carry(0xC8A7, 255, false); + assert_eq!(result, 0xC8A7); + assert_eq!(carry, false); + } + + #[test] + fn test_ror() { + let (result, carry) = Cpu::ror_u8_with_carry(0xAA, 8); + assert_eq!(result, 0xAA); + assert_eq!(carry, true); + + let (result, carry) = Cpu::ror_u8_with_carry(0x01, 1); + assert_eq!(result, 0x80); + assert_eq!(carry, true); + } +} diff --git a/core/src/cpu_vx0/biu.rs b/core/src/cpu_vx0/biu.rs new file mode 100644 index 00000000..4e841ce0 --- /dev/null +++ b/core/src/cpu_vx0/biu.rs @@ -0,0 +1,940 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::biu.rs + + Implement CPU behavior specific to the BIU (Bus Interface Unit) + +*/ +use crate::{ + bytequeue::*, + cpu_common::{operands::OperandSize, QueueOp, Segment}, + cpu_vx0::*, +}; + +pub const QUEUE_SIZE: usize = 4; +pub const QUEUE_POLICY_LEN: usize = 3; + +pub enum ReadWriteFlag { + Normal, + RNI, +} + +impl ByteQueue for NecVx0 { + fn seek(&mut self, _pos: usize) { + // Instruction queue does not support seeking + } + + fn tell(&self) -> usize { + //log::trace!("pc: {:05X} qlen: {}", self.pc, self.queue.len()); + //self.pc as usize - (self.queue.len() + (self.queue.has_preload() as usize)) + self.pc as usize - (self.queue.len_p()) + } + + // No microcode available for VX0 + fn set_pc(&mut self, pc: u16) {} + + fn wait(&mut self, cycles: u32) { + self.cycles(cycles); + } + + fn wait_i(&mut self, cycles: u32, instr: &[u16]) { + self.cycles_i(cycles, instr); + } + + fn wait_comment(&mut self, comment: &'static str) { + self.trace_comment(comment); + } + + fn q_read_u8(&mut self, dtype: QueueType, reader: QueueReader) -> u8 { + self.biu_queue_read(dtype, reader) + } + + fn q_read_i8(&mut self, dtype: QueueType, reader: QueueReader) -> i8 { + self.biu_queue_read(dtype, reader) as i8 + } + + fn q_read_u16(&mut self, dtype: QueueType, reader: QueueReader) -> u16 { + let lo = self.biu_queue_read(dtype, reader); + let ho = self.biu_queue_read(QueueType::Subsequent, reader); + + (ho as u16) << 8 | (lo as u16) + } + + fn q_read_i16(&mut self, dtype: QueueType, reader: QueueReader) -> i16 { + let lo = self.biu_queue_read(dtype, reader); + let ho = self.biu_queue_read(QueueType::Subsequent, reader); + + ((ho as u16) << 8 | (lo as u16)) as i16 + } + + fn q_peek_u8(&mut self) -> u8 { + let (byte, _cost) = self.bus.read_u8(self.flat_ip() as usize, 0).unwrap(); + byte + } + + fn q_peek_i8(&mut self) -> i8 { + let (byte, _cost) = self.bus.read_u8(self.flat_ip() as usize, 0).unwrap(); + byte as i8 + } + + fn q_peek_u16(&mut self) -> u16 { + let (word, _cost) = self.bus.read_u16(self.flat_ip() as usize, 0).unwrap(); + word + } + + fn q_peek_i16(&mut self) -> i16 { + let (word, _cost) = self.bus.read_u16(self.flat_ip() as usize, 0).unwrap(); + word as i16 + } + + fn q_peek_farptr16(&mut self) -> (u16, u16) { + let read_offset = self.flat_ip() as usize; + + let (offset, _cost) = self.bus.read_u16(read_offset, 0).unwrap(); + let (segment, _cost) = self.bus.read_u16(read_offset + 2, 0).unwrap(); + (segment, offset) + } +} + +impl NecVx0 { + /// Read a byte from the instruction queue. + /// Either return a byte currently in the queue, or fetch a byte into the queue and + /// then return it. + /// + /// Regardless of 8088 or 8086, the queue is read from one byte at a time. + /// + /// QueueType is used to set the QS status lines for first/subsequent byte fetches. + /// QueueReader is used to advance the microcode instruction if the queue read is + /// from the EU executing an instruction. The BIU reading the queue to fetch an + /// instruction will not advance the microcode PC. + pub fn biu_queue_read(&mut self, dtype: QueueType, reader: QueueReader) -> u8 { + let byte; + //trace_print!(self, "biu_queue_read()"); + + if let Some(preload_byte) = self.queue.get_preload() { + // We have a preloaded byte from finalizing the last instruction. + self.last_queue_op = QueueOp::First; + self.last_queue_byte = preload_byte; + + if self.nx { + self.nx = false; + } + + self.biu_fetch_on_queue_read(); + return preload_byte; + } + + if self.queue.len() > 0 { + // The queue has an available byte. Return it. + + //self.trace_print("biu_queue_read: pop()"); + //self.trace_comment("Q_READ"); + byte = self.queue.pop(); + self.biu_fetch_on_queue_read(); + } + else { + // Queue is empty, wait for a byte to be fetched into the queue then return it. + // Fetching is automatic, therefore, just cycle the cpu until a byte appears... + while self.queue.len() == 0 { + self.cycle(); + } + + // ...and pop it out. + byte = self.queue.pop(); + } + + self.queue_byte = byte; + + // TODO: These enums duplicate functionality + self.queue_op = match dtype { + QueueType::First => QueueOp::First, + QueueType::Subsequent => { + match reader { + QueueReader::Biu => QueueOp::Subsequent, + QueueReader::Eu => { + // Advance the microcode PC. + QueueOp::Subsequent + } + } + } + }; + + self.cycle(); + byte + } + + #[inline] + pub fn biu_fetch_on_queue_read(&mut self) { + if self.queue.at_policy_len() { + self.trace_comment("FETCH_ON_READ"); + self.biu_fetch_start(); + } + } + + /// This function will cycle the CPU until a byte is available in the instruction queue, + /// then read it out, to prepare for execution of the next instruction. + /// + /// We consider this byte 'preloaded' - this does not correspond to a real CPU state + pub fn biu_fetch_next(&mut self) { + // Don't fetch if we are in a string instruction that is still repeating. + if !self.in_rep { + self.trace_comment("FETCH"); + let mut fetch_timeout = 0; + + /* + if MICROCODE_FLAGS_8088[self.mc_pc as usize] == RNI { + trace_print!(self, "Executed terminating RNI!"); + } + */ + + if self.queue.len() == 0 { + while { + if self.nx { + self.trace_comment("NX"); + self.nx = false; + self.rni = false; + } + self.cycle(); + fetch_timeout += 1; + if fetch_timeout == 20 { + self.trace_flush(); + panic!("FETCH timeout! wait states: {}", self.wait_states); + } + self.queue.len() == 0 + } {} + // Should be a byte in the queue now. Preload it + self.queue.set_preload(); + self.queue_op = QueueOp::First; + + // Check if reading the queue will initiate a new fetch. + self.biu_fetch_on_queue_read(); + + self.trace_comment("FETCH_END"); + self.cycle(); + } + else { + self.queue.set_preload(); + self.queue_op = QueueOp::First; + + // Check if reading the queue will initiate a new fetch. + self.biu_fetch_on_queue_read(); + + if self.nx { + self.trace_comment("NX"); + } + + if self.rni { + self.trace_comment("RNI"); + self.rni = false; + } + + self.trace_comment("FETCH_END"); + self.cycle(); + } + } + } + + /// Implements the SUSP microcode routine to suspend prefetching. SUSP does not + /// return until the end of any current bus cycle. + pub fn biu_fetch_suspend(&mut self) { + self.trace_comment("SUSP"); + self.fetch_state = FetchState::Suspended; + + // SUSP waits for any current fetch to complete. + if self.bus_status_latch == BusStatus::CodeFetch { + self.biu_bus_wait_finish(); + } + + // Reset pipeline status or we will hang + self.ta_cycle = TaCycle::Td; + self.pl_status = BusStatus::Passive; + } + + /// Simulate the logic that disables prefetching in the halt state. This is not exactly the same + /// logic as used to suspend prefetching via SUSP. + pub fn biu_fetch_halt(&mut self) { + self.trace_comment("HALT_FETCH"); + + match self.t_cycle { + TCycle::T1 | TCycle::T2 => { + // We have time to prevent a prefetch decision. + self.fetch_state = FetchState::Halted; + } + _ => { + // We halted too late - a prefetch will be attempted. + } + } + } + + /// Implement the FLUSH microcode subroutine to flush the instruction queue. This will trigger + /// a new fetch cycle immediately to refill the queue. + pub fn biu_queue_flush(&mut self) { + self.queue.flush(); + self.queue_op = QueueOp::Flush; + self.trace_comment("FLUSH"); + self.fetch_state = FetchState::Normal; + + // Start a new prefetch address cycle + self.biu_fetch_start(); + } + + pub fn biu_queue_has_room(&mut self) -> bool { + match self.cpu_type { + CpuType::NecV20 => self.queue.len() < QUEUE_SIZE, + CpuType::NecV30 => { + // 8086 fetches two bytes at a time, so must be two free bytes in queue + self.queue.len() < QUEUE_SIZE - 1 + } + _ => { + panic!("Unsupported CPU subtype") + } + } + } + + /// Decide whether to start a code fetch this cycle. Should be called at Ti and end of T2. + pub fn biu_make_fetch_decision(&mut self) { + if matches!(self.fetch_state, FetchState::Delayed(0)) && self.biu_queue_has_room() { + // If fetch_state is Delayed, we can assume this is being called on Ti. + self.trace_comment("FETCH_RESUME"); + self.biu_fetch_start(); + //self.fetch_state = FetchState::Normal; + return; + } + else if self.bus_status == BusStatus::CodeFetch + && self.queue.at_policy_len() + && self.bus_pending != BusPendingType::EuEarly + { + self.fetch_state = FetchState::Delayed(3); + } + + if !self.biu_queue_has_room() { + // Queue is full, suspend fetching. + self.fetch_state = FetchState::PausedFull; + } + else if self.bus_pending != BusPendingType::EuEarly + && self.fetch_state != FetchState::Suspended + && !(self.queue.at_policy_len() && self.bus_status_latch == BusStatus::CodeFetch) + { + // EU has not claimed the bus this m-cycle, and we are not at a queue policy length + // during a code fetch, and fetching is not suspended. Begin a code fetch address cycle. + self.biu_fetch_start(); + } + } + + /// Issue a HALT. HALT is a unique bus status code, but not a real bus state. It is hacked + /// in by miscellaneous logic for one cycle. + pub fn biu_halt(&mut self) { + self.fetch_state = FetchState::Halted; + self.biu_bus_wait_finish(); + if let TCycle::T4 = self.t_cycle { + self.cycle(); + } + self.t_cycle = TCycle::Ti; + self.cycle(); + + self.bus_status = BusStatus::Halt; + self.bus_status_latch = BusStatus::Halt; + self.bus_segment = Segment::CS; + self.t_cycle = TCycle::T1; + self.i8288.ale = true; + self.data_bus = 0; + self.transfer_size = self.fetch_size; + self.operand_size = OperandSize::Operand8; + self.transfer_n = 1; + self.final_transfer = true; + + self.cycle(); + } + + /// Issue an interrupt acknowledge, consisting of two consecutive INTA bus cycles. + pub fn biu_inta(&mut self, vector: u8) { + self.biu_bus_begin( + BusStatus::InterruptAck, + Segment::None, + 0, + 0, + TransferSize::Byte, + OperandSize::Operand16, + true, + ); + + self.biu_bus_wait_finish(); + + self.biu_bus_begin( + BusStatus::InterruptAck, + Segment::None, + 0, + vector as u16, + TransferSize::Byte, + OperandSize::Operand16, + false, + ); + + self.biu_bus_wait_finish(); + } + + pub fn biu_read_u8(&mut self, seg: Segment, offset: u16) -> u8 { + let addr = self.calc_linear_address_seg(seg, offset); + + self.biu_bus_begin( + BusStatus::MemRead, + seg, + addr, + 0, + TransferSize::Byte, + OperandSize::Operand8, + true, + ); + let _cycles_waited = self.biu_bus_wait_finish(); + + (self.data_bus & 0x00FF) as u8 + } + + pub fn biu_write_u8(&mut self, seg: Segment, offset: u16, byte: u8, flag: ReadWriteFlag) { + let addr = self.calc_linear_address_seg(seg, offset); + + self.biu_bus_begin( + BusStatus::MemWrite, + seg, + addr, + byte as u16, + TransferSize::Byte, + OperandSize::Operand8, + true, + ); + match flag { + ReadWriteFlag::Normal => self.biu_bus_wait_finish(), + ReadWriteFlag::RNI => self.biu_bus_wait_until_tx(), + }; + } + + pub fn biu_io_read_u8(&mut self, addr: u16) -> u8 { + self.biu_bus_begin( + BusStatus::IoRead, + Segment::None, + addr as u32, + 0, + TransferSize::Byte, + OperandSize::Operand8, + true, + ); + let _cycles_waited = self.biu_bus_wait_finish(); + + (self.data_bus & 0x00FF) as u8 + } + + pub fn biu_io_write_u8(&mut self, addr: u16, byte: u8, flag: ReadWriteFlag) { + self.biu_bus_begin( + BusStatus::IoWrite, + Segment::None, + addr as u32, + byte as u16, + TransferSize::Byte, + OperandSize::Operand8, + true, + ); + match flag { + ReadWriteFlag::Normal => self.biu_bus_wait_finish(), + ReadWriteFlag::RNI => self.biu_bus_wait_until_tx(), + }; + + //validate_write_u8!(self, addr, (self.data_bus & 0x00FF) as u8); + } + + pub fn biu_io_read_u16(&mut self, addr: u16, flag: ReadWriteFlag) -> u16 { + let mut word; + + self.biu_bus_begin( + BusStatus::IoRead, + Segment::None, + addr as u32, + 0, + TransferSize::Byte, + OperandSize::Operand16, + true, + ); + self.biu_bus_wait_finish(); + + word = self.data_bus & 0x00FF; + + self.biu_bus_begin( + BusStatus::IoRead, + Segment::None, + addr.wrapping_add(1) as u32, + 0, + TransferSize::Byte, + OperandSize::Operand16, + false, + ); + + match flag { + ReadWriteFlag::Normal => self.biu_bus_wait_finish(), + ReadWriteFlag::RNI => self.biu_bus_wait_until_tx(), + }; + + word |= (self.data_bus & 0x00FF) << 8; + + word + } + + pub fn biu_io_write_u16(&mut self, addr: u16, word: u16, flag: ReadWriteFlag) { + self.biu_bus_begin( + BusStatus::IoWrite, + Segment::None, + addr as u32, + word & 0x00FF, + TransferSize::Byte, + OperandSize::Operand16, + true, + ); + + self.biu_bus_wait_finish(); + + self.biu_bus_begin( + BusStatus::IoWrite, + Segment::None, + addr.wrapping_add(1) as u32, + (word >> 8) & 0x00FF, + TransferSize::Byte, + OperandSize::Operand16, + false, + ); + + match flag { + ReadWriteFlag::Normal => self.biu_bus_wait_finish(), + ReadWriteFlag::RNI => self.biu_bus_wait_until_tx(), + }; + } + + /// Request a word size (16-bit) bus read transfer from the BIU. + /// The 8088 divides word transfers up into two consecutive byte size transfers. + pub fn biu_read_u16(&mut self, seg: Segment, offset: u16, flag: ReadWriteFlag) -> u16 { + let mut word; + let mut addr = self.calc_linear_address_seg(seg, offset); + + self.biu_bus_begin( + BusStatus::MemRead, + seg, + addr, + 0, + TransferSize::Byte, + OperandSize::Operand16, + true, + ); + + self.biu_bus_wait_finish(); + word = self.data_bus & 0x00FF; + addr = self.calc_linear_address_seg(seg, offset.wrapping_add(1)); + + self.biu_bus_begin( + BusStatus::MemRead, + seg, + addr, + 0, + TransferSize::Byte, + OperandSize::Operand16, + false, + ); + match flag { + ReadWriteFlag::Normal => self.biu_bus_wait_finish(), + ReadWriteFlag::RNI => { + // self.bus_wait_until(TCycle::T3) + self.biu_bus_wait_finish() + } + }; + word |= (self.data_bus & 0x00FF) << 8; + + word + } + + /// Request a word size (16-bit) bus write transfer from the BIU. + /// The 8088 divides word transfers up into two consecutive byte size transfers. + pub fn biu_write_u16(&mut self, seg: Segment, offset: u16, word: u16, flag: ReadWriteFlag) { + let mut addr = self.calc_linear_address_seg(seg, offset); + + // 8088 performs two consecutive byte transfers + self.biu_bus_begin( + BusStatus::MemWrite, + seg, + addr, + word & 0x00FF, + TransferSize::Byte, + OperandSize::Operand16, + true, + ); + + self.biu_bus_wait_finish(); + addr = self.calc_linear_address_seg(seg, offset.wrapping_add(1)); + + self.biu_bus_begin( + BusStatus::MemWrite, + seg, + addr, + (word >> 8) & 0x00FF, + TransferSize::Byte, + OperandSize::Operand16, + false, + ); + + match flag { + ReadWriteFlag::Normal => self.biu_bus_wait_finish(), + ReadWriteFlag::RNI => self.biu_bus_wait_until_tx(), + }; + } + + /// If in an active bus cycle, cycle the cpu until the bus cycle has reached T4. + #[inline] + pub fn biu_bus_wait_finish(&mut self) -> u32 { + let mut elapsed = 0; + + if let BusStatus::Passive = self.bus_status_latch { + // No bus cycle in progress + 0 + } + else { + while self.t_cycle != TCycle::T4 { + self.cycle(); + elapsed += 1; + } + elapsed + } + } + + /// If in a fetch delay, cycle the CPU until we are not (and set abort ta_cycle) + #[inline] + pub fn biu_bus_wait_delay(&mut self) -> bool { + let mut was_delay = false; + if let FetchState::Delayed(delay) = self.fetch_state { + self.cycles(delay as u32); + was_delay = true; + self.ta_cycle = TaCycle::Ta; + } + was_delay + } + + /// If an address cycle is in progress, cycle the cpu until the address cycle has reached Td + /// or has aborted. + #[inline] + pub fn biu_bus_wait_address(&mut self) -> u32 { + let mut elapsed = 0; + while !matches!(self.ta_cycle, TaCycle::Td | TaCycle::Ta) { + self.cycle(); + elapsed += 1; + } + elapsed + } + + /// If in an active bus cycle, cycle the cpu until the bus cycle has reached at least T2. + pub fn biu_bus_wait_halt(&mut self) -> u32 { + if matches!(self.bus_status_latch, BusStatus::Passive) && self.t_cycle == TCycle::T1 { + self.cycle(); + return 1; + } + 0 + } + + /// If in an active bus cycle, cycle the CPU until the target T-state is reached. + /// + /// This function is usually used on a terminal write to wait for T3-TwLast to + /// handle RNI in microcode. The next instruction byte will be fetched on this + /// terminating cycle and the beginning of execution will overlap with T4. + pub fn biu_bus_wait_until_tx(&mut self) -> u32 { + let mut bus_cycles_elapsed = 0; + return match self.bus_status_latch { + BusStatus::MemRead + | BusStatus::MemWrite + | BusStatus::IoRead + | BusStatus::IoWrite + | BusStatus::CodeFetch => { + self.trace_comment("WAIT_TX"); + while !self.is_last_wait() { + self.cycle(); + bus_cycles_elapsed += 1; + } + self.trace_comment("TX"); + /* + if target_state == TCycle::Tw { + // Interpret waiting for Tw as waiting for T3 or Last Tw + loop { + match (self.t_cycle, effective_wait_states) { + (TCycle::T3, 0) => { + self.trace_comment(" >> wait match!"); + if self.bus_wait_states == 0 { + self.trace_comment(">> no bus_wait_states"); + return bus_cycles_elapsed + } + else { + self.trace_comment(">> wait state!"); + self.cycle(); + } + } + (TCycle::T3, n) | (TCycle::Tw, n) => { + log::trace!("waits: {}", n); + for _ in 0..n { + self.cycle(); + bus_cycles_elapsed += 1; + } + return bus_cycles_elapsed + } + _ => { + self.cycle(); + bus_cycles_elapsed += 1; + } + } + } + } + else { + while self.t_cycle != target_state { + self.cycle(); + bus_cycles_elapsed += 1; + } + } + */ + + bus_cycles_elapsed + } + _ => 0, + }; + } + + /// Begins a new bus cycle of the specified type. + /// + /// This is a complex operation; we must wait for the current bus transfer, if any, to complete. + /// We must process any delays and penalties as appropriate. For example, if we initiate a + /// request for a bus cycle when a prefetch is scheduled, we will incur a 2 cycle abort penalty. + /// + /// Note: this function is for EU bus requests only. It cannot start a CODE fetch. + pub fn biu_bus_begin( + &mut self, + new_bus_status: BusStatus, + bus_segment: Segment, + address: u32, + data: u16, + size: TransferSize, + op_size: OperandSize, + first: bool, + ) { + self.trace_comment("BUS_BEGIN"); + + assert_ne!( + new_bus_status, + BusStatus::CodeFetch, + "Cannot start a CODE fetch with biu_bus_begin()" + ); + + // Check this address for a memory access breakpoint + if self.bus.get_flags(address as usize) & MEM_BPA_BIT != 0 { + // Breakpoint hit + self.state = CpuState::BreakpointHit; + } + + let mut fetch_abort = false; + + match self.t_cycle { + TCycle::Tinit => { + panic!("Can't start a bus cycle on Tinit") + } + TCycle::Ti => { + // Bus is idle, enter Tr state immediately. + self.biu_address_start(new_bus_status); + } + TCycle::T1 | TCycle::T2 => { + // We can enter Tr state immediately since we are requesting the bus before a + // fetch decision. + self.bus_pending = BusPendingType::EuEarly; + assert_eq!(self.ta_cycle, TaCycle::Td); + self.biu_address_start(new_bus_status); + } + _ => { + if matches!(self.pl_status, BusStatus::CodeFetch) { + self.bus_pending = BusPendingType::EuLate; + fetch_abort = true; + } + else if !self.final_transfer { + self.bus_pending = BusPendingType::EuEarly; + } + } + } + + // Wait for any current bus cycle to terminate. If no bus cycle is in progress, this is a + // no-op. + let _ = self.biu_bus_wait_finish(); + + // Wait for any fetch delay to complete. + let was_delay = self.biu_bus_wait_delay(); + + // Wait for address cycle to complete. + self.trace_comment("WAIT_ADDRESS"); + self.biu_bus_wait_address(); + self.trace_comment("WAIT_ADDRESS_DONE"); + + // If we aborted a fetch, begin the address cycle for the new bus cycle now and wait for it + // to complete. + if was_delay || fetch_abort { + self.biu_address_start(new_bus_status); + self.biu_bus_wait_address(); + } + + // If there was an active bus cycle, we're now on T4 - tick over to T1 to get + // ready to start the new bus cycle. + if self.t_cycle == TCycle::T4 && self.bus_status_latch != BusStatus::CodeFetch { + // We should be in a T0 or Td address state now; with any prefetch aborted. + //assert_eq!(self.ta_cycle, TaCycle::Td); + + self.cycle(); + } + + // Set the final_transfer flag if this is the last bus cycle of an atomic bus transfer + // (word read/writes cannot be interrupted by prefetches) + if let TransferSize::Word = size { + self.transfer_n = 1; + self.final_transfer = true; + } + else if first { + self.final_transfer = match op_size { + OperandSize::Operand8 => { + self.transfer_n = 1; + true + } + OperandSize::Operand16 => { + self.transfer_n = 1; + false + } + _ => panic!("invalid OperandSize"), + } + } + else { + // first == false is only possible if doing word transfer on 8088 + self.transfer_n = 2; + self.final_transfer = true; + } + + // Finally, begin the new bus state. + self.bus_pending = BusPendingType::None; + self.pl_status = BusStatus::Passive; // Pipeline status must always be reset on T1 + self.bus_pending = BusPendingType::None; // Can't be pending anymore as we're starting the EU cycle + self.bus_status = new_bus_status; + self.bus_status_latch = new_bus_status; + self.bus_segment = bus_segment; + self.t_cycle = TCycle::Tinit; + self.address_bus = address; + self.address_latch = address; + self.i8288.ale = true; + self.data_bus = data; + self.transfer_size = size; + self.operand_size = op_size; + } + + pub fn biu_bus_end(&mut self) { + // Reset i8288 signals + self.i8288.mrdc = false; + self.i8288.amwc = false; + self.i8288.mwtc = false; + self.i8288.iorc = false; + self.i8288.aiowc = false; + self.i8288.iowc = false; + + //self.bus_pending = BusPendingType::None; + } + + #[inline] + pub fn biu_fetch_start(&mut self) { + // Only start a fetch if the EU hasn't claimed the bus, and we're not already in a fetch + // address cycle. + if self.bus_pending != BusPendingType::EuEarly && self.pl_status != BusStatus::CodeFetch { + self.fetch_state = FetchState::Normal; + self.biu_address_start(BusStatus::CodeFetch); + } + } + + /// Start a new address cycle. This will set the Ta cycle to Tr and switch the pipeline slots. + /// ta_cycle should never be set to Tr outside of this function. + #[inline] + pub fn biu_address_start(&mut self, new_bus_status: BusStatus) { + // Switch pipeline slots and start a new address cycle. + if self.t_cycle != TCycle::Ti { + // If the bus is not idle, we usually want to change pipeline slots on a new address + // cycle, except when we abort a fetch. It is clearer to me to remain in the same + // slot on an abort. + if self.ta_cycle != TaCycle::Ta { + self.pl_slot = !self.pl_slot; + } + } + else { + self.pl_slot = false; + } + + // Skip Tr if we aborted. (T0 of abort was our Tr) + if self.ta_cycle == TaCycle::Ta { + self.ta_cycle = TaCycle::Ts; + } + else { + // Did not abort, start new address cycle at Tr + self.ta_cycle = TaCycle::Tr; + } + self.pl_status = new_bus_status; + self.trace_comment("ADDRESS_START"); + } + + /// Perform a prefetch abort. This should be called on T4 of a code fetch when a late eu bus + /// request is present. Whether this is a T0 or a Tr state is academic, the next cycle will be + /// a Ts state regardless. + #[inline] + pub fn biu_fetch_abort(&mut self) { + self.trace_comment("ABORT"); + self.ta_cycle = TaCycle::Ta; + } + + pub fn biu_fetch_bus_begin(&mut self) { + let addr = NecVx0::calc_linear_address(self.cs, self.pc); + if self.biu_queue_has_room() { + //trace_print!(self, "Setting address bus to PC: {:05X}", self.pc); + self.fetch_state = FetchState::Normal; + self.pl_status = BusStatus::Passive; // Pipeline status must always be reset on T1 + self.bus_status = BusStatus::CodeFetch; + self.bus_status_latch = BusStatus::CodeFetch; + self.bus_segment = Segment::CS; + self.t_cycle = TCycle::Tinit; + self.address_bus = addr; + self.address_latch = addr; + self.i8288.ale = true; + self.data_bus = 0; + self.transfer_size = self.fetch_size; + self.operand_size = match self.fetch_size { + TransferSize::Byte => OperandSize::Operand8, + TransferSize::Word => OperandSize::Operand16, + }; + self.transfer_n = 1; + self.final_transfer = true; + } + //else if !self.bus_pending_eu { + // Cancel fetch if queue is full and no pending bus request from EU that + // would otherwise trigger an abort. + //self.biu_abort_fetch_full(); + //} + } +} diff --git a/core/src/cpu_vx0/cpu.rs b/core/src/cpu_vx0/cpu.rs new file mode 100644 index 00000000..97199b07 --- /dev/null +++ b/core/src/cpu_vx0/cpu.rs @@ -0,0 +1,525 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::cpu.rs + + Implements the Cpu trait for the Intel 808x CPUs + +*/ + +use crate::{ + breakpoints::{BreakPointType, StopWatchData}, + bus::BusInterface, + bytequeue::ByteQueue, + cpu_common::{ + instruction::Instruction, + Cpu, + CpuAddress, + CpuDispatch, + CpuError, + CpuOption, + CpuStringState, + CpuType, + QueueOp, + Register8, + ServiceEvent, + StepResult, + TraceMode, + }, + cpu_validator::{CycleState, VRegisters}, + cpu_vx0::{ + trace_print, + BusStatus, + CpuState, + FetchState, + NecVx0, + Register16, + TCycle, + TaCycle, + CPU_FLAGS_RESERVED_ON, + }, + syntax_token::SyntaxToken, +}; + +#[cfg(feature = "cpu_validator")] +use crate::cpu_validator::CpuValidator; +#[cfg(feature = "cpu_validator")] +use crate::cpu_vx0::CpuValidatorState; + +impl Cpu for NecVx0 { + fn reset(&mut self) { + log::debug!("CPU Resetting..."); + /* + let trace_logger = std::mem::replace(&mut self.trace_logger, TraceLogger::None); + + // Save non-default values + *self = Self { + // Save parameters to new() + cpu_type: self.cpu_type, + reset_vector: self.reset_vector, + trace_mode: self.trace_mode, + trace_logger, + // Save options + instruction_history_on: self.instruction_history_on, + dram_refresh_simulation: self.dram_refresh_simulation, + halt_resume_delay: self.halt_resume_delay, + off_rails_detection: self.off_rails_detection, + enable_wait_states: self.enable_wait_states, + trace_enabled: self.trace_enabled, + + // Copy bus + bus: self.bus, + + #[cfg(feature = "cpu_validator")] + validator_type: ValidatorType, + #[cfg(feature = "cpu_validator")] + validator_trace: TraceLogger, + ..Self::default() + }; + */ + + self.state = CpuState::Normal; + + self.set_register16(Register16::AX, 0); + self.set_register16(Register16::BX, 0); + self.set_register16(Register16::CX, 0); + self.set_register16(Register16::DX, 0); + self.set_register16(Register16::SP, 0); + self.set_register16(Register16::BP, 0); + self.set_register16(Register16::SI, 0); + self.set_register16(Register16::DI, 0); + self.set_register16(Register16::ES, 0); + + self.set_register16(Register16::SS, 0); + self.set_register16(Register16::DS, 0); + + self.flags = CPU_FLAGS_RESERVED_ON; + + self.queue.flush(); + + if let CpuAddress::Segmented(segment, offset) = self.reset_vector { + self.set_register16(Register16::CS, segment); + self.set_register16(Register16::PC, offset); + } + else { + panic!("Invalid CpuAddress for reset vector."); + } + + self.address_latch = 0; + self.bus_status = BusStatus::Passive; + self.bus_status_latch = BusStatus::Passive; + self.t_cycle = TCycle::Ti; + self.ta_cycle = TaCycle::Td; + self.pl_status = BusStatus::Passive; + self.pl_slot = false; + + self.fetch_state = FetchState::Normal; + + self.instruction_count = 0; + self.int_count = 0; + self.iret_count = 0; + self.instr_cycle = 0; + self.cycle_num = 1; + self.halt_cycles = 0; + self.t_stamp = 0.0; + self.t_step = 0.00000021; + self.t_step_h = 0.000000105; + self.ready = true; + self.in_rep = false; + self.halted = false; + self.reported_halt = false; + self.halt_not_hold = false; + self.opcode0_counter = 0; + self.interrupt_inhibit = false; + self.intr_pending = false; + self.in_int = false; + self.is_error = false; + self.instruction_history.clear(); + self.call_stack.clear(); + self.int_flags = vec![0; 256]; + + self.instruction_reentrant = false; + self.last_ip = 0; + self.last_cs = 0; + self.last_intr = false; + self.jumped = false; + + self.queue_op = QueueOp::Idle; + self.last_queue_op = QueueOp::Idle; + + self.i8288.ale = false; + self.i8288.mrdc = false; + self.i8288.amwc = false; + self.i8288.mwtc = false; + self.i8288.iorc = false; + self.i8288.aiowc = false; + self.i8288.iowc = false; + + self.dram_refresh_tc = false; + self.dram_refresh_retrigger = false; + + self.step_over_target = None; + self.step_over_breakpoint = None; + self.end_addr = 0xFFFFF; + self.stopwatch_running = false; + + self.nx = false; + self.rni = false; + + self.halt_resume_delay = 4; + + // Reset takes 6 cycles before first fetch + self.cycle(); + self.biu_fetch_suspend(); + self.cycles_i(2, &[0x1e4, 0x1e5]); + self.biu_queue_flush(); + self.cycles_i(3, &[0x1e6, 0x1e7, 0x1e8]); + + #[cfg(feature = "cpu_validator")] + { + self.validator_state = CpuValidatorState::Uninitialized; + self.cycle_states.clear(); + } + + trace_print!(self, "Reset CPU! CS: {:04X} IP: {:04X}", self.cs, self.ip()); + } + + #[inline] + fn set_reset_vector(&mut self, address: CpuAddress) { + self.set_reset_vector(address); + } + + #[inline] + fn set_end_address(&mut self, address: CpuAddress) { + let end_addr; + match address { + CpuAddress::Segmented(segment, offset) => { + end_addr = NecVx0::calc_linear_address(segment, offset); + } + CpuAddress::Flat(addr) => { + end_addr = addr; + } + _ => { + panic!("Invalid CpuAddress for end address."); + } + } + self.set_end_address(end_addr as usize); + } + + #[inline] + fn set_nmi(&mut self, state: bool) { + self.set_nmi(state); + } + + #[inline] + fn set_intr(&mut self, state: bool) { + self.set_intr(state); + } + + #[inline] + fn step(&mut self, skip_breakpoint: bool) -> Result<(StepResult, u32), CpuError> { + self.step(skip_breakpoint) + } + + #[inline] + fn step_finish(&mut self) -> Result { + self.step_finish() + } + + #[inline] + fn in_rep(&self) -> bool { + self.in_rep + } + + #[inline] + fn get_type(&self) -> CpuType { + self.cpu_type + } + + #[inline] + fn get_ip(&mut self) -> u16 { + self.ip() + } + + #[inline] + fn get_register16(&self, reg: Register16) -> u16 { + self.get_register16(reg) + } + + #[inline] + fn set_register16(&mut self, reg: Register16, value: u16) { + self.set_register16(reg, value); + } + + #[inline] + fn get_register8(&self, reg: Register8) -> u8 { + self.get_register8(reg) + } + + #[inline] + fn set_register8(&mut self, reg: Register8, value: u8) { + self.set_register8(reg, value); + } + + #[inline] + fn get_flags(&self) -> u16 { + self.get_flags() + } + + #[inline] + fn set_flags(&mut self, flags: u16) { + self.set_flags(flags); + } + + #[inline] + fn get_cycle_ct(&self) -> (u64, u64) { + self.get_cycle_ct() + } + + #[inline] + fn get_instruction_ct(&self) -> u64 { + self.get_instruction_ct() + } + + /// Return the resolved flat address of CS:CORR(PC) + #[inline] + fn flat_ip(&self) -> u32 { + NecVx0::calc_linear_address(self.cs, self.ip()) + } + + /// Return the resolved flat address of CS:CORR(PC), adjusted for reentrant instructions + #[inline] + fn flat_ip_disassembly(&self) -> u32 { + NecVx0::calc_linear_address(self.cs, self.disassembly_ip()) + } + + #[inline] + fn flat_sp(&self) -> u32 { + self.flat_sp() + } + + #[inline] + fn dump_instruction_history_string(&self) -> String { + self.dump_instruction_history_string() + } + + #[inline] + fn dump_instruction_history_tokens(&self) -> Vec> { + self.dump_instruction_history_tokens() + } + + fn dump_call_stack(&self) -> String { + self.dump_call_stack() + } + + #[inline] + fn get_service_event(&mut self) -> Option { + self.service_events.pop_front() + } + + #[inline] + fn get_cycle_states(&self) -> &Vec { + self.get_cycle_states() + } + + fn get_cycle_trace(&self) -> &Vec { + self.get_cycle_trace() + } + + fn get_cycle_trace_tokens(&self) -> &Vec> { + self.get_cycle_trace_tokens() + } + + #[inline] + #[cfg(feature = "cpu_validator")] + fn get_vregisters(&self) -> VRegisters { + self.get_vregisters() + } + + #[inline] + fn get_string_state(&self) -> CpuStringState { + self.get_string_state() + } + + fn eval_address(&self, expr: &str) -> Option { + self.eval_address(expr) + } + + #[inline] + fn clear_breakpoint_flag(&mut self) { + self.clear_breakpoint_flag(); + } + + #[inline] + fn set_breakpoints(&mut self, bp_list: Vec) { + self.set_breakpoints(bp_list) + } + + #[inline] + fn get_step_over_breakpoint(&self) -> Option { + self.get_step_over_breakpoint() + } + + #[inline] + fn set_step_over_breakpoint(&mut self, address: CpuAddress) { + self.set_step_over_breakpoint(address) + } + + #[inline] + fn get_sw_data(&self) -> Vec { + self.get_sw_data() + } + + #[inline] + fn set_stopwatch(&mut self, sw_idx: usize, start: u32, stop: u32) { + self.set_stopwatch(sw_idx, start, stop) + } + + fn set_option(&mut self, opt: CpuOption) { + match opt { + CpuOption::InstructionHistory(state) => { + log::debug!("Setting InstructionHistory to: {:?}", state); + self.instruction_history.clear(); + self.instruction_history_on = state; + } + CpuOption::ScheduleInterrupt(_state, cycle_target, cycles, retrigger) => { + log::debug!("Setting InterruptHint to: ({},{})", cycle_target, cycles); + self.interrupt_scheduling = true; + self.interrupt_cycle_period = cycle_target; + self.interrupt_cycle_num = cycles; + self.interrupt_retrigger = retrigger; + } + CpuOption::ScheduleDramRefresh(state, cycle_target, cycles, retrigger) => { + log::trace!( + "Setting SimulateDramRefresh to: {:?} ({},{})", + state, + cycle_target, + cycles + ); + self.dram_refresh_simulation = state; + self.dram_refresh_cycle_period = cycle_target; + self.dram_refresh_cycle_num = cycles; + self.dram_refresh_retrigger = retrigger; + self.dram_refresh_tc = false; + } + CpuOption::DramRefreshAdjust(adj) => { + log::debug!("Setting DramRefreshAdjust to: {}", adj); + self.dram_refresh_adjust = adj; + } + CpuOption::HaltResumeDelay(delay) => { + log::debug!("Setting HaltResumeDelay to: {}", delay); + self.halt_resume_delay = delay; + } + CpuOption::OffRailsDetection(state) => { + log::debug!("Setting OffRailsDetection to: {:?}", state); + self.off_rails_detection = state; + } + CpuOption::EnableWaitStates(state) => { + log::debug!("Setting EnableWaitStates to: {:?}", state); + self.enable_wait_states = state; + } + CpuOption::TraceLoggingEnabled(state) => { + log::debug!("Setting TraceLoggingEnabled to: {:?}", state); + self.trace_enabled = state; + + // Flush the trace log file on stopping trace so that we can immediately + // see results otherwise buffered + if state == false { + self.trace_flush(); + } + } + CpuOption::EnableServiceInterrupt(state) => { + log::debug!("Setting EnableServiceInterrupt to: {:?}", state); + self.enable_service_interrupt = state; + } + } + } + + fn get_option(&self, opt: CpuOption) -> bool { + match opt { + CpuOption::InstructionHistory(_) => self.instruction_history_on, + CpuOption::ScheduleInterrupt(..) => self.interrupt_cycle_period > 0, + CpuOption::ScheduleDramRefresh(..) => self.dram_refresh_simulation, + CpuOption::DramRefreshAdjust(..) => true, + CpuOption::HaltResumeDelay(..) => true, + CpuOption::OffRailsDetection(_) => self.off_rails_detection, + CpuOption::EnableWaitStates(_) => self.enable_wait_states, + CpuOption::TraceLoggingEnabled(_) => self.trace_enabled, + CpuOption::EnableServiceInterrupt(_) => self.enable_service_interrupt, + } + } + + fn bus(&self) -> &BusInterface { + &self.bus + } + + fn bus_mut(&mut self) -> &mut BusInterface { + &mut self.bus + } + + // Logging methods + fn cycle_table_header(&self) -> Vec { + self.cycle_table_header() + } + + fn emit_header(&mut self) { + self.emit_header(); + } + + fn trace_flush(&mut self) { + self.trace_flush(); + } + + #[cfg(feature = "cpu_validator")] + fn get_validator(&self) -> &Option> { + self.get_validator() + } + + #[cfg(feature = "cpu_validator")] + fn get_validator_mut(&mut self) -> &mut Option> { + self.get_validator_mut() + } + + fn randomize_seed(&mut self, seed: u64) { + self.randomize_seed(seed); + } + + fn randomize_mem(&mut self) { + self.randomize_mem(); + } + + fn randomize_regs(&mut self) { + self.randomize_regs(); + } + + fn random_grp_instruction(&mut self, opcode: u8, extension_list: &[u8]) { + self.random_grp_instruction(opcode, extension_list) + } + + fn random_inst_from_opcodes(&mut self, opcode_list: &[u8]) { + self.random_inst_from_opcodes(opcode_list); + } +} diff --git a/core/src/cpu_vx0/cycle.rs b/core/src/cpu_vx0/cycle.rs new file mode 100644 index 00000000..ba03577c --- /dev/null +++ b/core/src/cpu_vx0/cycle.rs @@ -0,0 +1,633 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::cycle.rs + + Contains functions for cycling the cpu through its various states. + Cycle functions are called by instructions and bus logic whenever + the CPU should be ticked. + +*/ + +use crate::{cpu_common::QueueOp, cpu_vx0::*}; + +#[cfg(feature = "cpu_validator")] +use crate::cpu_validator::{BusType, ReadType}; + +macro_rules! validate_read_u8 { + ($myself: expr, $addr: expr, $data: expr, $btype: expr, $rtype: expr) => {{ + #[cfg(feature = "cpu_validator")] + if let Some(ref mut validator) = &mut $myself.validator { + validator.emu_read_byte($addr, $data, $btype, $rtype) + } + }}; +} + +macro_rules! validate_write_u8 { + ($myself: expr, $addr: expr, $data: expr, $btype: expr) => {{ + #[cfg(feature = "cpu_validator")] + if let Some(ref mut validator) = &mut $myself.validator { + validator.emu_write_byte($addr, $data, $btype) + } + }}; +} + +impl NecVx0 { + #[inline(always)] + pub fn cycle(&mut self) { + self.cycle_i(MC_NONE); + } + + /// Execute a CPU cycle. + /// 'instr' is the microcode line reference of the cycle being executed, if applicable + /// (otherwise it should be passed MC_NONE). + /// The CPU will transition between T-states, execute bus transfers on T3 or TW-last, + /// and otherwise do all necessary actions to advance the cpu state. + pub fn cycle_i(&mut self, instr: u16) { + if self.t_cycle == TCycle::Tinit { + self.t_cycle = TCycle::T1; + } + + // TODO: Can we refactor this so this isn't necessary? + if self.in_int { + self.int_elapsed += 1; + } + else { + self.instr_elapsed += 1; + } + + if self.cycle_num == 3480541 { + log::debug!("Cycle 352483"); + } + + // Operate current t-state + match self.bus_status_latch { + BusStatus::Passive => { + self.transfer_n = 0; + if let FetchState::Delayed(0) = self.fetch_state { + self.biu_make_fetch_decision(); + } + } + BusStatus::MemRead + | BusStatus::MemWrite + | BusStatus::IoRead + | BusStatus::IoWrite + | BusStatus::CodeFetch + | BusStatus::InterruptAck => { + match self.t_cycle { + TCycle::Tinit => { + panic!("Can't execute TInit state"); + } + TCycle::Ti => { + self.biu_make_fetch_decision(); + } + TCycle::T1 => {} + TCycle::T2 => { + // Turn off ale signal on T2 + self.i8288.ale = false; + + // Read/write signals go high on T2. + match self.bus_status_latch { + BusStatus::CodeFetch | BusStatus::MemRead => { + self.i8288.mrdc = true; + } + BusStatus::MemWrite => { + // Only AMWC goes high on T2. MWTC delayed to T3. + self.i8288.amwc = true; + } + BusStatus::IoRead => { + self.i8288.iorc = true; + } + BusStatus::IoWrite => { + // Only AIOWC goes high on T2. IOWC delayed to T3. + self.i8288.aiowc = true; + } + BusStatus::InterruptAck => { + self.i8288.inta = true; + if self.transfer_n == 0 { + self.lock = true; + } + } + _ => {} + } + + match self.bus_status_latch { + BusStatus::CodeFetch | BusStatus::MemRead => { + self.bus_wait_states = self + .bus + .get_read_wait(self.address_latch as usize, self.instr_elapsed) + .unwrap(); + } + BusStatus::MemWrite => { + self.bus_wait_states = self + .bus + .get_write_wait(self.address_latch as usize, self.instr_elapsed) + .unwrap(); + } + BusStatus::IoRead => { + self.bus_wait_states = 1; + } + BusStatus::IoWrite => { + self.bus_wait_states = 1; + } + _ => {} + } + + if !self.enable_wait_states { + //trace_print!(self, "Suppressing wait states!"); + self.bus_wait_states = 0; + self.wait_states = 0; + } + else if self.bus_wait_states > 0 { + self.ready = false; + } + + // A prefetch decision is made at the end of T2 of the last bus cycle of an atomic + // bus operation, regardless of wait states. + if self.final_transfer { + self.biu_make_fetch_decision(); + } + } + TCycle::T3 => { + if self.is_last_wait_t3tw() { + // Do bus transfer on T3 if no wait states. + self.do_bus_transfer(); + self.ready = true; + } + } + TCycle::Tw => { + if self.is_last_wait_t3tw() { + self.do_bus_transfer(); + self.ready = true; + } + } + TCycle::T4 => { + // If we just completed a code fetch, make the byte available in the queue. + if let BusStatus::CodeFetch = self.bus_status_latch { + self.queue.push8(self.data_bus as u8); + self.pc = self.pc.wrapping_add(1); + } + } + } + } + BusStatus::Halt => { + self.trace_comment("HALT"); + } + }; + + // Perform cycle tracing, if enabled + if self.trace_enabled { + self.do_cycle_trace(); + } + + #[cfg(feature = "cpu_validator")] + { + let cycle_state = self.get_cycle_state(); + self.cycle_states.push(cycle_state); + } + + // Do DRAM refresh (DMA channel 0) simulation + if self.enable_wait_states && self.dram_refresh_simulation { + self.tick_dma(); + } + + // Advance fetch delay counter + if let FetchState::Delayed(delay) = &mut self.fetch_state { + if self.t_cycle != TCycle::Tw { + *delay = delay.saturating_sub(1); + } + if *delay == 0 { + //self.fetch_state = FetchState::Normal; + } + } + + // Transition to next Ta state + self.ta_cycle = match self.ta_cycle { + TaCycle::Tr => { + // We can always proceed from Tr to Ts. + TaCycle::Ts + } + TaCycle::Ts => { + // We can proceed from Ts to T0 if there is not a late EU bus request. + /* match (self.pl_status, self.bus_pending) { + (BusStatus::CodeFetch, BusPendingType::EuLate) => TaCycle::Tr, + _ => TaCycle::T0, + }*/ + + TaCycle::T0 + } + TaCycle::T0 => { + // We can proceed from T0 to Td on T4 if the pending bus cycle is not a code fetch. + match (self.pl_status, self.bus_pending) { + (BusStatus::CodeFetch, BusPendingType::None) => { + // We can immediately end the address cycle on Ti or T4 if there is no pending eu request. + if matches!(self.t_cycle, TCycle::Ti | TCycle::T4) && self.fetch_state != FetchState::Suspended + { + self.biu_fetch_bus_begin(); + TaCycle::Td + } + else { + TaCycle::T0 + } + } + (BusStatus::CodeFetch, BusPendingType::EuLate) => { + // We have a late EU bus request. We will abort the code fetch, but only + // on T4. We will do nothing on T3; this implements the prefetch abort delay. + if matches!(self.t_cycle, TCycle::Ti | TCycle::T4) { + self.biu_fetch_abort(); + self.ta_cycle + } + else { + TaCycle::T0 + } + } + _ => { + // Not a code fetch - no abort handling required. Begin the next bus cycle at Ti or T4. + if matches!(self.t_cycle, TCycle::Ti | TCycle::T4) { + self.t_cycle = TCycle::Tinit; + TaCycle::Td + } + else { + TaCycle::T0 + } + } + } + } + TaCycle::Td => TaCycle::Td, + TaCycle::Ta => TaCycle::Ta, + }; + + // Transition to next T state + self.t_cycle = match self.t_cycle { + TCycle::Tinit => { + // A new bus cycle has been initiated, begin it in T1. + TCycle::T1 + } + TCycle::Ti => { + // If bus status is PASV, stay in Ti (no bus transfer occurring) + match self.bus_status_latch { + BusStatus::Passive => TCycle::Ti, + BusStatus::Halt => { + // Halt only lasts for one cycle. Reset status and ALE. + self.bus_status = BusStatus::Passive; + self.bus_status_latch = BusStatus::Passive; + self.i8288.ale = false; + TCycle::Ti + } + _ => TCycle::T1, + } + } + TCycle::T1 => { + // If there is a valid bus status on T1, transition to T2, unless + // status is HALT, which only lasts one cycle. + match self.bus_status_latch { + BusStatus::Passive => { + //panic!("T1 with passive bus"), + TCycle::T1 + } + BusStatus::Halt => { + // Halt only lasts for one cycle. Reset status and ALE. + self.bus_status = BusStatus::Passive; + self.bus_status_latch = BusStatus::Passive; + self.i8288.ale = false; + TCycle::Ti + } + _ => TCycle::T2, + } + } + TCycle::T2 => { + self.wait_states += self.bus_wait_states; + TCycle::T3 + } + TCycle::T3 => { + // If no wait states have been reported, advance to T3, otherwise go to Tw + if self.wait_states > 0 || self.dma_wait_states > 0 { + self.wait_states = self.wait_states.saturating_sub(1); + TCycle::Tw + } + else { + self.biu_bus_end(); + TCycle::T4 + } + } + TCycle::Tw => { + // If we are handling wait states, continue in Tw (decrement at end of cycle) + // If we have handled all wait states, transition to T4 + if self.wait_states > 0 || self.dma_wait_states > 0 { + self.wait_states = self.wait_states.saturating_sub(1); + //log::debug!("wait states: {}", self.wait_states); + TCycle::Tw + } + else { + self.biu_bus_end(); + TCycle::T4 + } + } + TCycle::T4 => { + // We reached the end of a bus transfer, to transition back to Ti and PASV. + self.bus_status_latch = BusStatus::Passive; + TCycle::Ti + } + }; + + // Reset queue operation + self.last_queue_op = self.queue_op; + self.last_queue_byte = self.queue_byte; + self.queue_op = QueueOp::Idle; + + self.instr_cycle += 1; + self.device_cycles += 1; + + self.cycle_num += 1; + if self.cycle_num & 1 == 0 { + self.clk0 = !self.clk0; + } + self.dma_wait_states = self.dma_wait_states.saturating_sub(1); + + if self.wait_states == 0 && self.dma_wait_states == 0 { + self.ready = true; + } + + // Advance timestamp 210ns. + self.t_stamp += self.t_step; + self.last_queue_len = self.queue.len(); + } + + /// Advance the DMA scheduler by one tick. This function is called every CPU tick. Since it is + /// only called from within cycle_i() it can be inlined. + #[inline(always)] + pub fn tick_dma(&mut self) { + self.dram_refresh_cycle_num = self.dram_refresh_cycle_num.saturating_sub(1); + + // Reset scheduler at terminal count + if self.dram_refresh_cycle_num == 0 && !self.dram_refresh_tc { + // The DACK0 signal suppresses generation of DREQ0, but we advance DMA state after this. + // So we will use HOLDA as the suppression signal to give us one cycle advance notice of !DACK0. + self.dram_refresh_tc = true; + self.dma_req = !self.dma_holda; + + if self.dram_refresh_retrigger { + self.dram_refresh_tc = false; + self.dram_refresh_cycle_num = self.dram_refresh_cycle_period; + } + } + + match &mut self.dma_state { + DmaState::Idle => { + if self.dma_req { + // DRAM refresh cycle counter has hit terminal count. + // Begin DMA transfer simulation by entering DREQ state. + self.dma_state = DmaState::Dreq; + } + } + DmaState::Dreq => { + // DMA request triggered on DMA controller. Next cycle, DMA controller + // will assert HRQ (Hold Request) + self.dma_state = DmaState::Hrq; + } + DmaState::Hrq => { + // DMA Hold Request. + // DMA Hold request waits for issuance of HOLDA (Hold Acknowledge) + // This signal is generated by miscellaneous TTL logic when S0 & S1 state + // indicates PASV or HALT and !LOCK. (1,1) (Note, bus goes PASV after T2) + + if (self.bus_status == BusStatus::Passive) + || match self.t_cycle { + TCycle::T3 | TCycle::Tw | TCycle::T4 => true, + _ => false, + } + { + // S0 & S1 are idle. Issue hold acknowledge if LOCK not asserted. + if !self.lock { + self.dma_state = DmaState::HoldA; + self.dma_holda = true; + } + } + } + DmaState::HoldA => { + // DMA Hold Acknowledge has been issued. DMA controller will enter S1 + // on next cycle. + self.dma_state = DmaState::Operating(0); + self.dma_aen = true; + } + DmaState::Operating(cycles) => { + // the DMA controller has control of the bus now. + // Run DMA transfer cycles. + *cycles = *cycles + 1; + match *cycles { + 1 => { + // DMAWAIT asserted after S1 + self.dma_wait_states = 7; // Effectively 6 as this is decremented this cycle + self.ready = false; + } + 2 => { + // DACK asserted after S2 + self.dma_req = false; + self.dma_ack = true; + } + 4 => { + // Transfer cycles have elapsed, so move to end state. + self.dma_holda = false; + } + 5 => { + self.dma_aen = false; + self.dma_ack = false; + self.dma_state = DmaState::Idle; + } + _ => {} + } + } + DmaState::End => { + // DMA transfer has completed. Deassert DACK and reset state to idle. + + self.dma_state = DmaState::Idle; + } + } + } + + pub fn do_bus_transfer(&mut self) { + let byte; + + match (self.bus_status_latch, self.transfer_size) { + (BusStatus::CodeFetch, TransferSize::Byte) => { + (byte, _) = self + .bus + .read_u8(self.address_latch as usize, self.instr_elapsed) + .unwrap(); + self.data_bus = byte as u16; + + validate_read_u8!( + self, + self.address_latch, + (self.data_bus & 0x00FF) as u8, + BusType::Mem, + ReadType::Code + ); + } + (BusStatus::CodeFetch, TransferSize::Word) => { + (self.data_bus, _) = self + .bus + .read_u16(self.address_latch as usize, self.instr_elapsed) + .unwrap(); + } + (BusStatus::MemRead, TransferSize::Byte) => { + (byte, _) = self + .bus + .read_u8(self.address_latch as usize, self.instr_elapsed) + .unwrap(); + self.instr_elapsed = 0; + self.data_bus = byte as u16; + + validate_read_u8!( + self, + self.address_latch, + (self.data_bus & 0x00FF) as u8, + BusType::Mem, + ReadType::Data + ); + } + (BusStatus::MemRead, TransferSize::Word) => { + (self.data_bus, _) = self + .bus + .read_u16(self.address_latch as usize, self.instr_elapsed) + .unwrap(); + self.instr_elapsed = 0; + } + (BusStatus::MemWrite, TransferSize::Byte) => { + self.i8288.mwtc = true; + _ = self + .bus + .write_u8( + self.address_latch as usize, + (self.data_bus & 0x00FF) as u8, + self.instr_elapsed, + ) + .unwrap(); + self.instr_elapsed = 0; + + validate_write_u8!(self, self.address_latch, (self.data_bus & 0x00FF) as u8, BusType::Mem); + } + (BusStatus::MemWrite, TransferSize::Word) => { + self.i8288.mwtc = true; + _ = self + .bus + .write_u16(self.address_latch as usize, self.data_bus, self.instr_elapsed) + .unwrap(); + self.instr_elapsed = 0; + } + (BusStatus::IoRead, TransferSize::Byte) => { + self.i8288.iorc = true; + byte = self + .bus + .io_read_u8((self.address_latch & 0xFFFF) as u16, self.instr_elapsed); + self.data_bus = byte as u16; + self.instr_elapsed = 0; + + validate_read_u8!( + self, + self.address_latch, + (self.data_bus & 0x00FF) as u8, + BusType::Io, + ReadType::Data + ); + } + (BusStatus::IoWrite, TransferSize::Byte) => { + self.i8288.iowc = true; + self.bus.io_write_u8( + (self.address_latch & 0xFFFF) as u16, + (self.data_bus & 0x00FF) as u8, + self.instr_elapsed, + ); + self.instr_elapsed = 0; + + validate_write_u8!(self, self.address_latch, (self.data_bus & 0x00FF) as u8, BusType::Io); + } + (BusStatus::InterruptAck, TransferSize::Byte) => { + // The vector is read from the PIC directly before we even enter an INTA bus state, so there's + // nothing to do. + + //log::debug!("in INTA transfer_n: {}", self.transfer_n); + // Deassert lock + if self.transfer_n == 2 { + //log::debug!("deasserting lock! transfer_n: {}", self.transfer_n); + self.lock = false; + self.intr = false; + } + //self.transfer_n += 1; + } + _ => { + trace_print!(self, "Unhandled bus state!"); + log::warn!("Unhandled bus status: {:?}!", self.bus_status_latch); + } + } + + self.bus_status = BusStatus::Passive; + self.address_bus = (self.address_bus & !0xFF) | (self.data_bus as u32); + } + + #[inline] + pub fn cycle_nx(&mut self) { + self.nx = true; + } + + #[inline] + pub fn cycle_nx_i(&mut self, _instr: u16) { + self.nx = true; + } + + #[inline] + pub fn cycles(&mut self, ct: u32) { + for _ in 0..ct { + self.cycle(); + } + } + + #[inline] + pub fn cycles_i(&mut self, ct: u32, instrs: &[u16]) { + for i in 0..ct as usize { + self.cycle_i(instrs[i]); + } + } + + #[inline] + pub fn cycles_nx(&mut self, ct: u32) { + self.cycles(ct - 1); + self.nx = true; + //self.cycles(ct); + } + + #[inline] + pub fn cycles_nx_i(&mut self, ct: u32, instrs: &[u16]) { + self.cycles_i(ct - 1, instrs); + self.nx = true; + //self.cycles_i(ct, instrs); + } + + #[cfg(feature = "cpu_validator")] + pub fn get_cycle_states(&self) -> &Vec { + &self.cycle_states + } +} diff --git a/core/src/cpu_vx0/decode.rs b/core/src/cpu_vx0/decode.rs new file mode 100644 index 00000000..b8d1ca13 --- /dev/null +++ b/core/src/cpu_vx0/decode.rs @@ -0,0 +1,786 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::decode.rs + + Opcode fetch and instruction decoding routines. + + This module is implemented as an associated function, decode(), which + operates on implementors of ByteQueue. This allows instruction decoding + from either the processor instruction queue emulation, or directly + from emulator memory for our debug disassembly viewer. + +*/ + +use std::{error::Error, fmt::Display}; + +use crate::cpu_vx0::{modrm::ModRmByte, *}; + +use crate::{ + bytequeue::*, + cpu_vx0::{alu::Xi, gdr::GdrEntry}, + cpu_common::{AddressingMode, Instruction}, +}; +use crate::cpu_common::{Mnemonic, Segment, OperandType}; +use crate::cpu_common::operands::OperandSize; + +#[derive(Copy, Clone, PartialEq)] +pub enum OperandTemplate { + NoTemplate, + NoOperand, + ModRM8, + ModRM16, + Register8, + Register16, + SegmentRegister, + Register8Encoded, + Register16Encoded, + Immediate8, + Immediate16, + Immediate8SignExtended, + Relative8, + Relative16, + Offset8, + Offset16, + FixedRegister8(Register8), + FixedRegister16(Register16), + //NearAddress, + FarAddress, +} + +type Ot = OperandTemplate; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum InstructionDecodeError { + UnsupportedOpcode(u8), + InvalidSegmentRegister, + ReadOutOfBounds, + GeneralDecodeError(u8), + Unimplemented(u8), +} + +impl Error for InstructionDecodeError {} +impl Display for InstructionDecodeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + InstructionDecodeError::UnsupportedOpcode(o) => { + write!(f, "An unsupported opcode was encountered: {:#2x}.", o) + } + InstructionDecodeError::InvalidSegmentRegister => { + write!(f, "An invalid segment register was specified.") + } + InstructionDecodeError::ReadOutOfBounds => { + write!(f, "Unexpected buffer exhaustion while decoding instruction.") + } + InstructionDecodeError::GeneralDecodeError(o) => { + write!(f, "General error decoding opcode {:#2x}.", o) + } + InstructionDecodeError::Unimplemented(o) => { + write!(f, "Decoding of instruction {:#2x} not implemented.", o) + } + } + } +} + +pub struct InstTemplate { + pub grp: u8, + pub gdr: GdrEntry, + pub mc: u16, + pub xi: Option, + pub mnemonic: Mnemonic, + pub operand1: OperandTemplate, + pub operand2: OperandTemplate, +} + +macro_rules! inst { + ($op:literal, $grp:literal, $gdr:literal, $mc:literal, $xi:ident, $m:ident, $o1:expr, $o2:expr) => { + InstTemplate { + grp: $grp, + gdr: GdrEntry($gdr), + mc: $mc, + xi: Some(Xi::$xi), + mnemonic: Mnemonic::$m, + operand1: $o1, + operand2: $o2, + } + }; + ($op:literal, $grp:literal, $gdr:literal, $mc:literal, $m:ident, $o1:expr, $o2:expr) => { + InstTemplate { + grp: $grp, + gdr: GdrEntry($gdr), + mc: $mc, + xi: None, + mnemonic: Mnemonic::$m, + operand1: $o1, + operand2: $o2, + } + }; +} + +#[rustfmt::skip] +pub const DECODE: [InstTemplate; 352] = [ + inst!( 0x00, 0, 0b0100101000000000, 0x008, ADD , ADD, Ot::ModRM8, Ot::Register8), + inst!( 0x01, 0, 0b0100101000000000, 0x008, ADD , ADD, Ot::ModRM16, Ot::Register16), + inst!( 0x02, 0, 0b0100101000000000, 0x008, ADD , ADD, Ot::Register8, Ot::ModRM8), + inst!( 0x03, 0, 0b0100101000000000, 0x008, ADD , ADD, Ot::Register16, Ot::ModRM16), + inst!( 0x04, 0, 0b0100100010010010, 0x018, ADD , ADD, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), + inst!( 0x05, 0, 0b0100100010010010, 0x018, ADD , ADD, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), + inst!( 0x06, 0, 0b0100000000110010, 0x02c, PUSH, Ot::FixedRegister16(Register16::ES), Ot::NoOperand), + inst!( 0x07, 0, 0b0100000000110010, 0x038, POP, Ot::FixedRegister16(Register16::ES), Ot::NoOperand), + inst!( 0x08, 0, 0b0100101000000000, 0x008, OR , OR, Ot::ModRM8, Ot::Register8), + inst!( 0x09, 0, 0b0100101000000000, 0x008, OR , OR, Ot::ModRM16, Ot::Register16), + inst!( 0x0A, 0, 0b0100101000000000, 0x008, OR , OR, Ot::Register8, Ot::ModRM8), + inst!( 0x0B, 0, 0b0100101000000000, 0x008, OR , OR, Ot::Register16, Ot::ModRM16), + inst!( 0x0C, 0, 0b0100100010010010, 0x018, OR , OR, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), + inst!( 0x0D, 0, 0b0100100010010010, 0x018, OR , OR, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), + inst!( 0x0E, 0, 0b0100000000110010, 0x02c, PUSH, Ot::FixedRegister16(Register16::CS), Ot::NoOperand), + inst!( 0x0F, 0, 0b0100000000110010, 0x038, POP, Ot::FixedRegister16(Register16::CS), Ot::NoOperand), + inst!( 0x10, 0, 0b0100101000000000, 0x008, ADC , ADC, Ot::ModRM8, Ot::Register8), + inst!( 0x11, 0, 0b0100101000000000, 0x008, ADC , ADC, Ot::ModRM16, Ot::Register16), + inst!( 0x12, 0, 0b0100101000000000, 0x008, ADC , ADC, Ot::Register8, Ot::ModRM8), + inst!( 0x13, 0, 0b0100101000000000, 0x008, ADC , ADC, Ot::Register16, Ot::ModRM16), + inst!( 0x14, 0, 0b0100100010010010, 0x018, ADC , ADC, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), + inst!( 0x15, 0, 0b0100100010010010, 0x018, ADC , ADC, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), + inst!( 0x16, 0, 0b0100000000110010, 0x02c, PUSH, Ot::FixedRegister16(Register16::SS), Ot::NoOperand), + inst!( 0x17, 0, 0b0100000000110010, 0x038, POP, Ot::FixedRegister16(Register16::SS), Ot::NoOperand), + inst!( 0x18, 0, 0b0100101000000000, 0x008, SBB , SBB, Ot::ModRM8, Ot::Register8), + inst!( 0x19, 0, 0b0100101000000000, 0x008, SBB , SBB, Ot::ModRM16, Ot::Register16), + inst!( 0x1A, 0, 0b0100101000000000, 0x008, SBB , SBB, Ot::Register8, Ot::ModRM8), + inst!( 0x1B, 0, 0b0100101000000000, 0x008, SBB , SBB, Ot::Register16, Ot::ModRM16), + inst!( 0x1C, 0, 0b0100100010010010, 0x018, SBB , SBB, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), + inst!( 0x1D, 0, 0b0100100010010010, 0x018, SBB , SBB, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), + inst!( 0x1E, 0, 0b0100000000110010, 0x02c, PUSH, Ot::FixedRegister16(Register16::DS), Ot::NoOperand), + inst!( 0x1F, 0, 0b0100000000110010, 0x038, POP, Ot::FixedRegister16(Register16::DS), Ot::NoOperand), + inst!( 0x20, 0, 0b0100101000000000, 0x008, AND , AND, Ot::ModRM8, Ot::Register8), + inst!( 0x21, 0, 0b0100101000000000, 0x008, AND , AND, Ot::ModRM16, Ot::Register16), + inst!( 0x22, 0, 0b0100101000000000, 0x008, AND , AND, Ot::Register8, Ot::ModRM8), + inst!( 0x23, 0, 0b0100101000000000, 0x008, AND , AND, Ot::Register16, Ot::ModRM16), + inst!( 0x24, 0, 0b0100100010010010, 0x018, AND , AND, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), + inst!( 0x25, 0, 0b0100100010010010, 0x018, AND , AND, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), + inst!( 0x26, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand), + inst!( 0x27, 0, 0b0101000000110010, 0x144, DAA , DAA, Ot::NoOperand, Ot::NoOperand), + inst!( 0x28, 0, 0b0100101000000000, 0x008, SUB , SUB, Ot::ModRM8, Ot::Register8), + inst!( 0x29, 0, 0b0100101000000000, 0x008, SUB , SUB, Ot::ModRM16, Ot::Register16), + inst!( 0x2A, 0, 0b0100101000000000, 0x008, SUB , SUB, Ot::Register8, Ot::ModRM8), + inst!( 0x2B, 0, 0b0100101000000000, 0x008, SUB , SUB, Ot::Register16, Ot::ModRM16), + inst!( 0x2C, 0, 0b0100100010010010, 0x018, SUB , SUB, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), + inst!( 0x2D, 0, 0b0100100010010010, 0x018, SUB , SUB, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), + inst!( 0x2E, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand), + inst!( 0x2F, 0, 0b0101000000110010, 0x144, DAS , DAS, Ot::NoOperand, Ot::NoOperand), + inst!( 0x30, 0, 0b0100101000000000, 0x008, XOR , XOR, Ot::ModRM8, Ot::Register8), + inst!( 0x31, 0, 0b0100101000000000, 0x008, XOR , XOR, Ot::ModRM16, Ot::Register16), + inst!( 0x32, 0, 0b0100101000000000, 0x008, XOR , XOR, Ot::Register8, Ot::ModRM8), + inst!( 0x33, 0, 0b0100101000000000, 0x008, XOR , XOR, Ot::Register16, Ot::ModRM16), + inst!( 0x34, 0, 0b0100100010010010, 0x018, XOR , XOR, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), + inst!( 0x35, 0, 0b0100100010010010, 0x018, XOR , XOR, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), + inst!( 0x36, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand), + inst!( 0x37, 0, 0b0101000000110010, 0x148, AAA , AAA, Ot::NoOperand, Ot::NoOperand), + inst!( 0x38, 0, 0b0100101000000000, 0x008, CMP , CMP, Ot::ModRM8, Ot::Register8), + inst!( 0x39, 0, 0b0100101000000000, 0x008, CMP , CMP, Ot::ModRM16, Ot::Register16), + inst!( 0x3A, 0, 0b0100101000000000, 0x008, CMP , CMP, Ot::Register8, Ot::ModRM8), + inst!( 0x3B, 0, 0b0100101000000000, 0x008, CMP , CMP, Ot::Register16, Ot::ModRM16), + inst!( 0x3C, 0, 0b0100100010010010, 0x018, CMP , CMP, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), + inst!( 0x3D, 0, 0b0100100010010010, 0x018, CMP , CMP, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), + inst!( 0x3E, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand), + inst!( 0x3F, 0, 0b0101000000110010, 0x148, AAS , AAS, Ot::NoOperand, Ot::NoOperand), + inst!( 0x40, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x41, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x42, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x43, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x44, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x45, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x46, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x47, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x48, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x49, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x4A, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x4B, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x4C, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x4D, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x4E, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x4F, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x50, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x51, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x52, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x53, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x54, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x55, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x56, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x57, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x58, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x59, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x5A, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x5B, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x5C, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x5D, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x5E, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x5F, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), + inst!( 0x60, 0, 0b0000000000010000, 0x0e8, PUSHA, Ot::NoOperand, Ot::NoOperand), + inst!( 0x61, 0, 0b0000000000010000, 0x0e8, POPA, Ot::NoOperand, Ot::NoOperand), + inst!( 0x62, 0, 0b0000000000011000, 0x0e8, BOUND, Ot::NoOperand, Ot::NoOperand), + inst!( 0x63, 0, 0b0000000000011000, 0x0e8, BOUND, Ot::NoOperand, Ot::NoOperand), + inst!( 0x64, 0, 0b0000000000010000, 0x0e8, Prefix, Ot::NoOperand, Ot::NoOperand), + inst!( 0x65, 0, 0b0000000000010000, 0x0e8, Prefix, Ot::NoOperand, Ot::NoOperand), + inst!( 0x66, 0, 0b0000000000000000, 0x0e8, ESC, Ot::NoOperand, Ot::NoOperand), + inst!( 0x67, 0, 0b0000000000000000, 0x0e8, ESC, Ot::NoOperand, Ot::NoOperand), + inst!( 0x68, 0, 0b0000000000000000, 0x0e8, PUSH, Ot::NoOperand, Ot::NoOperand), + inst!( 0x69, 0, 0b0000000000000000, 0x0e8, IMUL, Ot::NoOperand, Ot::NoOperand), + inst!( 0x6A, 0, 0b0000000000000000, 0x0e8, PUSH, Ot::NoOperand, Ot::NoOperand), + inst!( 0x6B, 0, 0b0000000000000000, 0x0e8, IMUL, Ot::NoOperand, Ot::NoOperand), + inst!( 0x6C, 0, 0b0000000000000000, 0x0e8, INS, Ot::NoOperand, Ot::NoOperand), + inst!( 0x6D, 0, 0b0000000000000000, 0x0e8, INS, Ot::NoOperand, Ot::NoOperand), + inst!( 0x6E, 0, 0b0000000000000000, 0x0e8, OUTS, Ot::NoOperand, Ot::NoOperand), + inst!( 0x6F, 0, 0b0000000000000000, 0x0e8, OUTS, Ot::NoOperand, Ot::NoOperand), + inst!( 0x70, 0, 0b0000000000110010, 0x0e8, JO, Ot::Relative8, Ot::NoOperand), + inst!( 0x71, 0, 0b0000000000110010, 0x0e8, JNO, Ot::Relative8, Ot::NoOperand), + inst!( 0x72, 0, 0b0000000000110010, 0x0e8, JB, Ot::Relative8, Ot::NoOperand), + inst!( 0x73, 0, 0b0000000000110010, 0x0e8, JNB, Ot::Relative8, Ot::NoOperand), + inst!( 0x74, 0, 0b0000000000110010, 0x0e8, JZ, Ot::Relative8, Ot::NoOperand), + inst!( 0x75, 0, 0b0000000000110010, 0x0e8, JNZ, Ot::Relative8, Ot::NoOperand), + inst!( 0x76, 0, 0b0000000000110010, 0x0e8, JBE, Ot::Relative8, Ot::NoOperand), + inst!( 0x77, 0, 0b0000000000110010, 0x0e8, JNBE, Ot::Relative8, Ot::NoOperand), + inst!( 0x78, 0, 0b0000000000110010, 0x0e8, JS, Ot::Relative8, Ot::NoOperand), + inst!( 0x79, 0, 0b0000000000110010, 0x0e8, JNS, Ot::Relative8, Ot::NoOperand), + inst!( 0x7A, 0, 0b0000000000110010, 0x0e8, JP, Ot::Relative8, Ot::NoOperand), + inst!( 0x7B, 0, 0b0000000000110010, 0x0e8, JNP, Ot::Relative8, Ot::NoOperand), + inst!( 0x7C, 0, 0b0000000000110010, 0x0e8, JL, Ot::Relative8, Ot::NoOperand), + inst!( 0x7D, 0, 0b0000000000110010, 0x0e8, JNL, Ot::Relative8, Ot::NoOperand), + inst!( 0x7E, 0, 0b0000000000110010, 0x0e8, JLE, Ot::Relative8, Ot::NoOperand), + inst!( 0x7F, 0, 0b0000000000110010, 0x0e8, JNLE, Ot::Relative8, Ot::NoOperand), + inst!( 0x80, 1, 0b0110100000000000, 0x00c, ADD , Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0x81, 2, 0b0110100000000000, 0x00c, CMP , Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0x82, 3, 0b0110100000000000, 0x00c, ADD , Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0x83, 4, 0b0110100000000000, 0x00c, CMP , Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0x84, 0, 0b0110100000000000, 0x094, TEST, Ot::ModRM8, Ot::Register8), + inst!( 0x85, 0, 0b0110100000000000, 0x094, TEST, Ot::ModRM16, Ot::Register16), + inst!( 0x86, 0, 0b0110100000000000, 0x0a4, XCHG, Ot::Register8, Ot::ModRM8), + inst!( 0x87, 0, 0b0110100000000000, 0x0a4, XCHG, Ot::Register16, Ot::ModRM16), + inst!( 0x88, 0, 0b0100101000100010, 0x000, MOV, Ot::ModRM8, Ot::Register8), + inst!( 0x89, 0, 0b0100101000100010, 0x000, MOV, Ot::ModRM16, Ot::Register16), + inst!( 0x8A, 0, 0b0100101000100000, 0x000, MOV, Ot::Register8, Ot::ModRM8), + inst!( 0x8B, 0, 0b0100101000100000, 0x000, MOV, Ot::Register16, Ot::ModRM16), + inst!( 0x8C, 0, 0b0100001100100010, 0x0ec, MOV, Ot::ModRM16, Ot::SegmentRegister), + inst!( 0x8D, 0, 0b0100000000100010, 0x004, LEA, Ot::Register16, Ot::ModRM16), + inst!( 0x8E, 0, 0b0100001100100000, 0x0ec, MOV, Ot::SegmentRegister, Ot::ModRM16), + inst!( 0x8F, 0, 0b0100000000100010, 0x040, POP, Ot::ModRM16, Ot::NoOperand), + inst!( 0x90, 0, 0b0100000000110010, 0x084, NOP, Ot::NoOperand, Ot::NoOperand), + inst!( 0x91, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)), + inst!( 0x92, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)), + inst!( 0x93, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)), + inst!( 0x94, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)), + inst!( 0x95, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)), + inst!( 0x96, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)), + inst!( 0x97, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)), + inst!( 0x98, 0, 0b0100000000110010, 0x054, CBW, Ot::NoOperand, Ot::NoOperand), + inst!( 0x99, 0, 0b0100000000110010, 0x058, CWD, Ot::NoOperand, Ot::NoOperand), + inst!( 0x9A, 0, 0b0100000000110010, 0x070, CALLF, Ot::FarAddress, Ot::NoOperand), + inst!( 0x9B, 0, 0b0100000000110010, 0x0f8, WAIT, Ot::NoOperand, Ot::NoOperand), + inst!( 0x9C, 0, 0b0100000000110010, 0x030, PUSHF, Ot::NoOperand, Ot::NoOperand), + inst!( 0x9D, 0, 0b0100000000110010, 0x03c, POPF, Ot::NoOperand, Ot::NoOperand), + inst!( 0x9E, 0, 0b0100000000110010, 0x100, SAHF, Ot::NoOperand, Ot::NoOperand), + inst!( 0x9F, 0, 0b0100000000110010, 0x104, LAHF, Ot::NoOperand, Ot::NoOperand), + inst!( 0xA0, 0, 0b0100100010110010, 0x060, MOV, Ot::FixedRegister8(Register8::AL), Ot::Offset8), + inst!( 0xA1, 0, 0b0100100010110010, 0x060, MOV, Ot::FixedRegister16(Register16::AX), Ot::Offset16), + inst!( 0xA2, 0, 0b0100100010110010, 0x064, MOV, Ot::Offset8, Ot::FixedRegister8(Register8::AL)), + inst!( 0xA3, 0, 0b0100100010110010, 0x064, MOV, Ot::Offset16, Ot::FixedRegister16(Register16::AX)), + inst!( 0xA4, 0, 0b0100100010110010, 0x12c, MOVSB, Ot::NoOperand, Ot::NoOperand), + inst!( 0xA5, 0, 0b0100100010110010, 0x12c, MOVSW, Ot::NoOperand, Ot::NoOperand), + inst!( 0xA6, 0, 0b0100100010110010, 0x120, CMPSB, Ot::NoOperand, Ot::NoOperand), + inst!( 0xA7, 0, 0b0100100010110010, 0x120, CMPSW, Ot::NoOperand, Ot::NoOperand), + inst!( 0xA8, 0, 0b0100100010110010, 0x09C, TEST, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), + inst!( 0xA9, 0, 0b0100100010110010, 0x09C, TEST, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), + inst!( 0xAA, 0, 0b0100100010110010, 0x11c, STOSB, Ot::NoOperand, Ot::NoOperand), + inst!( 0xAB, 0, 0b0100100010110010, 0x11c, STOSW, Ot::NoOperand, Ot::NoOperand), + inst!( 0xAC, 0, 0b0100100010110010, 0x12c, LODSB, Ot::NoOperand, Ot::NoOperand), + inst!( 0xAD, 0, 0b0100100010110010, 0x12c, LODSW, Ot::NoOperand, Ot::NoOperand), + inst!( 0xAE, 0, 0b0100100010110010, 0x120, SCASB, Ot::NoOperand, Ot::NoOperand), + inst!( 0xAF, 0, 0b0100100010110010, 0x120, SCASW, Ot::NoOperand, Ot::NoOperand), + inst!( 0xB0, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), + inst!( 0xB1, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), + inst!( 0xB2, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), + inst!( 0xB3, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), + inst!( 0xB4, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), + inst!( 0xB5, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), + inst!( 0xB6, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), + inst!( 0xB7, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), + inst!( 0xB8, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), + inst!( 0xB9, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), + inst!( 0xBA, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), + inst!( 0xBB, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), + inst!( 0xBC, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), + inst!( 0xBD, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), + inst!( 0xBE, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), + inst!( 0xBF, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), + inst!( 0xC0, 0, 0b0100000000110000, 0x0cc, RETN, Ot::Immediate16, Ot::NoOperand), + inst!( 0xC1, 0, 0b0100000000110000, 0x0bc, RETN, Ot::NoOperand, Ot::NoOperand), + inst!( 0xC2, 0, 0b0100000000110000, 0x0cc, RETN, Ot::Immediate16, Ot::NoOperand), + inst!( 0xC3, 0, 0b0100000000110000, 0x0bc, RETN, Ot::NoOperand, Ot::NoOperand), + inst!( 0xC4, 0, 0b0100000000100000, 0x0f0, LES, Ot::Register16, Ot::ModRM16), + inst!( 0xC5, 0, 0b0100000000100000, 0x0f4, LDS, Ot::Register16, Ot::ModRM16), + inst!( 0xC6, 0, 0b0100100000100010, 0x014, MOV, Ot::ModRM8, Ot::Immediate8), + inst!( 0xC7, 0, 0b0100100000100010, 0x014, MOV, Ot::ModRM16, Ot::Immediate16), + inst!( 0xC8, 0, 0b0100000000110000, 0x0cc, RETF, Ot::Immediate16, Ot::NoOperand), + inst!( 0xC9, 0, 0b0100000000110000, 0x0c0, RETF, Ot::NoOperand, Ot::NoOperand), + inst!( 0xCA, 0, 0b0100000000110000, 0x0cc, RETF, Ot::Immediate16, Ot::NoOperand), + inst!( 0xCB, 0, 0b0100000000110000, 0x0c0, RETF, Ot::NoOperand, Ot::NoOperand), + inst!( 0xCC, 0, 0b0100000000110000, 0x1b0, INT3, Ot::NoOperand, Ot::NoOperand), + inst!( 0xCD, 0, 0b0100000000110000, 0x1a8, INT, Ot::Immediate8, Ot::NoOperand), + inst!( 0xCE, 0, 0b0100000000110000, 0x1ac, INTO, Ot::NoOperand, Ot::NoOperand), + inst!( 0xCF, 0, 0b0100000000110000, 0x0c8, IRET, Ot::NoOperand, Ot::NoOperand), + inst!( 0xD0, 5, 0b0100100000000000, 0x088, ROL , Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0xD1, 6, 0b0100100000000000, 0x088, SAR , Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0xD2, 7, 0b0100100000000000, 0x08c, ROL , Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0xD3, 8, 0b0100100000000000, 0x08c, SAR , Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0xD4, 0, 0b0101000000110000, 0x174, AAM, Ot::Immediate8, Ot::NoOperand), + inst!( 0xD5, 0, 0b0101000000110000, 0x170, AAD, Ot::Immediate8, Ot::NoOperand), + inst!( 0xD6, 0, 0b0101000000110000, 0x0a0, SALC, Ot::NoOperand, Ot::NoOperand), + inst!( 0xD7, 0, 0b0101000000110000, 0x10c, XLAT, Ot::NoOperand, Ot::NoOperand), + inst!( 0xD8, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), + inst!( 0xD9, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), + inst!( 0xDA, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), + inst!( 0xDB, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), + inst!( 0xDC, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), + inst!( 0xDD, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), + inst!( 0xDE, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), + inst!( 0xDF, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), + inst!( 0xE0, 0, 0b0110000000110000, 0x138, LOOPNE, Ot::Relative8, Ot::NoOperand), + inst!( 0xE1, 0, 0b0110000000110000, 0x138, LOOPE, Ot::Relative8, Ot::NoOperand), + inst!( 0xE2, 0, 0b0110000000110000, 0x140, LOOP, Ot::Relative8, Ot::NoOperand), + inst!( 0xE3, 0, 0b0110000000110000, 0x134, JCXZ, Ot::Relative8, Ot::NoOperand), + inst!( 0xE4, 0, 0b0100100010110011, 0x0ac, IN, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), + inst!( 0xE5, 0, 0b0100100010110011, 0x0ac, IN, Ot::FixedRegister16(Register16::AX), Ot::Immediate8), + inst!( 0xE6, 0, 0b0100100010110011, 0x0b0, OUT, Ot::Immediate8, Ot::FixedRegister8(Register8::AL)), + inst!( 0xE7, 0, 0b0100100010110011, 0x0b0, OUT, Ot::Immediate8, Ot::FixedRegister16(Register16::AX)), + inst!( 0xE8, 0, 0b0110000000110000, 0x07c, CALL, Ot::Relative16, Ot::NoOperand), + inst!( 0xE9, 0, 0b0110000000110000, 0x0d0, JMP, Ot::Relative16, Ot::NoOperand), + inst!( 0xEA, 0, 0b0110000000110000, 0x0e0, JMPF, Ot::FarAddress, Ot::NoOperand), + inst!( 0xEB, 0, 0b0110000000110000, 0x0d0, JMP, Ot::Relative8, Ot::NoOperand), + inst!( 0xEC, 0, 0b0100100010110011, 0x0b4, IN, Ot::FixedRegister8(Register8::AL), Ot::FixedRegister16(Register16::DX)), + inst!( 0xED, 0, 0b0100100010110011, 0x0b4, IN, Ot::FixedRegister16(Register16::AX), Ot::FixedRegister16(Register16::DX)), + inst!( 0xEE, 0, 0b0100100010110011, 0x0b8, OUT, Ot::FixedRegister16(Register16::DX), Ot::FixedRegister8(Register8::AL)), + inst!( 0xEF, 0, 0b0100100010110011, 0x0b8, OUT, Ot::FixedRegister16(Register16::DX), Ot::FixedRegister16(Register16::AX)), + inst!( 0xF0, 0, 0b0100010000111010, 0x1FF, LOCK, Ot::NoOperand, Ot::NoOperand), + inst!( 0xF1, 0, 0b0100010000111010, 0x1FF, LOCK, Ot::NoOperand, Ot::NoOperand), + inst!( 0xF2, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand), + inst!( 0xF3, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand), + inst!( 0xF4, 0, 0b0100010000110010, 0x1FF, HLT, Ot::NoOperand, Ot::NoOperand), + inst!( 0xF5, 0, 0b0100010000110010, 0x1FF, CMC, Ot::NoOperand, Ot::NoOperand), + inst!( 0xF6, 9, 0b0100100000100100, 0x098, Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0xF7, 10, 0b0100100000100100, 0x160, Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0xF8, 0, 0b0100010001110010, 0x1FF, CLC, Ot::NoOperand, Ot::NoOperand), + inst!( 0xF9, 0, 0b0100010001110010, 0x1FF, STC, Ot::NoOperand, Ot::NoOperand), + inst!( 0xFA, 0, 0b0100010001110010, 0x1FF, CLI, Ot::NoOperand, Ot::NoOperand), + inst!( 0xFB, 0, 0b0100010001110010, 0x1FF, STI, Ot::NoOperand, Ot::NoOperand), + inst!( 0xFC, 0, 0b0100010001110010, 0x1FF, CLD, Ot::NoOperand, Ot::NoOperand), + inst!( 0xFD, 0, 0b0100010001110010, 0x1FF, STD, Ot::NoOperand, Ot::NoOperand), + inst!( 0xFE, 11, 0b0000100000100100, 0x020, Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0xFF, 12, 0b0000100000100100, 0x026, Group, Ot::NoOperand, Ot::NoOperand), + // Group + inst!( 0x80, 1, 0b0110100000000000, 0x00c, ADD , ADD , Ot::ModRM8, Ot::Immediate8), + inst!( 0x80, 1, 0b0110100000000000, 0x00c, OR , OR , Ot::ModRM8, Ot::Immediate8), + inst!( 0x80, 1, 0b0110100000000000, 0x00c, ADC , ADC , Ot::ModRM8, Ot::Immediate8), + inst!( 0x80, 1, 0b0110100000000000, 0x00c, SBB , SBB , Ot::ModRM8, Ot::Immediate8), + inst!( 0x80, 1, 0b0110100000000000, 0x00c, AND , AND , Ot::ModRM8, Ot::Immediate8), + inst!( 0x80, 1, 0b0110100000000000, 0x00c, SUB , SUB , Ot::ModRM8, Ot::Immediate8), + inst!( 0x80, 1, 0b0110100000000000, 0x00c, XOR , XOR , Ot::ModRM8, Ot::Immediate8), + inst!( 0x80, 1, 0b0110100000000000, 0x00c, CMP , CMP , Ot::ModRM8, Ot::Immediate8), + // Group + inst!( 0x81, 1, 0b0110100000000000, 0x00c, ADD , ADD , Ot::ModRM16, Ot::Immediate16), + inst!( 0x81, 1, 0b0110100000000000, 0x00c, OR , OR , Ot::ModRM16, Ot::Immediate16), + inst!( 0x81, 1, 0b0110100000000000, 0x00c, ADC , ADC , Ot::ModRM16, Ot::Immediate16), + inst!( 0x81, 1, 0b0110100000000000, 0x00c, SBB , SBB , Ot::ModRM16, Ot::Immediate16), + inst!( 0x81, 1, 0b0110100000000000, 0x00c, AND , AND , Ot::ModRM16, Ot::Immediate16), + inst!( 0x81, 1, 0b0110100000000000, 0x00c, SUB , SUB , Ot::ModRM16, Ot::Immediate16), + inst!( 0x81, 1, 0b0110100000000000, 0x00c, XOR , XOR , Ot::ModRM16, Ot::Immediate16), + inst!( 0x81, 1, 0b0110100000000000, 0x00c, CMP , CMP , Ot::ModRM16, Ot::Immediate16), + // Group + inst!( 0x82, 1, 0b0110100000000000, 0x00c, ADD , ADD , Ot::ModRM8, Ot::Immediate8), + inst!( 0x82, 1, 0b0110100000000000, 0x00c, OR , OR , Ot::ModRM8, Ot::Immediate8), + inst!( 0x82, 1, 0b0110100000000000, 0x00c, ADC , ADC , Ot::ModRM8, Ot::Immediate8), + inst!( 0x82, 1, 0b0110100000000000, 0x00c, SBB , SBB , Ot::ModRM8, Ot::Immediate8), + inst!( 0x82, 1, 0b0110100000000000, 0x00c, AND , AND , Ot::ModRM8, Ot::Immediate8), + inst!( 0x82, 1, 0b0110100000000000, 0x00c, SUB , SUB , Ot::ModRM8, Ot::Immediate8), + inst!( 0x82, 1, 0b0110100000000000, 0x00c, XOR , XOR , Ot::ModRM8, Ot::Immediate8), + inst!( 0x82, 1, 0b0110100000000000, 0x00c, CMP , CMP , Ot::ModRM8, Ot::Immediate8), + // Group + inst!( 0x83, 1, 0b0110100000000000, 0x00c, ADD , ADD , Ot::ModRM16, Ot::Immediate8SignExtended), + inst!( 0x83, 1, 0b0110100000000000, 0x00c, OR , OR , Ot::ModRM16, Ot::Immediate8SignExtended), + inst!( 0x83, 1, 0b0110100000000000, 0x00c, ADC , ADC , Ot::ModRM16, Ot::Immediate8SignExtended), + inst!( 0x83, 1, 0b0110100000000000, 0x00c, SBB , SBB , Ot::ModRM16, Ot::Immediate8SignExtended), + inst!( 0x83, 1, 0b0110100000000000, 0x00c, AND , AND , Ot::ModRM16, Ot::Immediate8SignExtended), + inst!( 0x83, 1, 0b0110100000000000, 0x00c, SUB , SUB , Ot::ModRM16, Ot::Immediate8SignExtended), + inst!( 0x83, 1, 0b0110100000000000, 0x00c, XOR , XOR , Ot::ModRM16, Ot::Immediate8SignExtended), + inst!( 0x83, 1, 0b0110100000000000, 0x00c, CMP , CMP , Ot::ModRM16, Ot::Immediate8SignExtended), + // Group + inst!( 0xD0, 2, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM8, Ot::NoOperand), + inst!( 0xD0, 2, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM8, Ot::NoOperand), + inst!( 0xD0, 2, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM8, Ot::NoOperand), + inst!( 0xD0, 2, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM8, Ot::NoOperand), + inst!( 0xD0, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM8, Ot::NoOperand), + inst!( 0xD0, 2, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM8, Ot::NoOperand), + inst!( 0xD0, 2, 0b0100100000000000, 0x088, SETMO , SETMO, Ot::ModRM8, Ot::NoOperand), + inst!( 0xD0, 2, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM8, Ot::NoOperand), + // Group + inst!( 0xD1, 2, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM16, Ot::NoOperand), + inst!( 0xD1, 2, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM16, Ot::NoOperand), + inst!( 0xD1, 2, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM16, Ot::NoOperand), + inst!( 0xD1, 2, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM16, Ot::NoOperand), + inst!( 0xD1, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM16, Ot::NoOperand), + inst!( 0xD1, 2, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM16, Ot::NoOperand), + inst!( 0xD1, 2, 0b0100100000000000, 0x088, SETMO , SETMO, Ot::ModRM16, Ot::NoOperand), + inst!( 0xD1, 2, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM16, Ot::NoOperand), + // Group + inst!( 0xD2, 3, 0b0100100000000000, 0x08c, ROL , ROL , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD2, 3, 0b0100100000000000, 0x08c, ROR , ROR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD2, 3, 0b0100100000000000, 0x08c, RCL , RCL , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD2, 3, 0b0100100000000000, 0x08c, RCR , RCR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD2, 3, 0b0100100000000000, 0x08c, SHL , SHL , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD2, 3, 0b0100100000000000, 0x08c, SHR , SHR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD2, 3, 0b0100100000000000, 0x08c, SETMO , SETMOC, Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD2, 3, 0b0100100000000000, 0x08c, SAR , SAR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + // Group + inst!( 0xD3, 3, 0b0100100000000000, 0x08c, ROL , ROL , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD3, 3, 0b0100100000000000, 0x08c, ROR , ROR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD3, 3, 0b0100100000000000, 0x08c, RCL , RCL , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD3, 3, 0b0100100000000000, 0x08c, RCR , RCR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD3, 3, 0b0100100000000000, 0x08c, SHL , SHL , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD3, 3, 0b0100100000000000, 0x08c, SHR , SHR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD3, 3, 0b0100100000000000, 0x08c, SETMO , SETMOC, Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD3, 3, 0b0100100000000000, 0x08c, SAR , SAR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + // Group + inst!( 0xF6, 4, 0b0100100000100100, 0x098, TEST , Ot::ModRM8, Ot::Immediate8), + inst!( 0xF6, 4, 0b0100100000100100, 0x098, TEST , Ot::ModRM8, Ot::Immediate8), + inst!( 0xF6, 4, 0b0100100000100100, 0x098, NOT , NOT , Ot::ModRM8, Ot::NoOperand), + inst!( 0xF6, 4, 0b0100100000100100, 0x098, NEG , NEG , Ot::ModRM8, Ot::NoOperand), + inst!( 0xF6, 4, 0b0100100000100100, 0x098, MUL , Ot::ModRM8, Ot::NoOperand), + inst!( 0xF6, 4, 0b0100100000100100, 0x098, IMUL , Ot::ModRM8, Ot::NoOperand), + inst!( 0xF6, 4, 0b0100100000100100, 0x098, DIV , Ot::ModRM8, Ot::NoOperand), + inst!( 0xF6, 4, 0b0100100000100100, 0x098, IDIV , Ot::ModRM8, Ot::NoOperand), + // Group + inst!( 0xF7, 4, 0b0100100000100100, 0x160, TEST , Ot::ModRM16, Ot::Immediate16), + inst!( 0xF7, 4, 0b0100100000100100, 0x160, TEST , Ot::ModRM16, Ot::Immediate16), + inst!( 0xF7, 4, 0b0100100000100100, 0x160, NOT , NOT , Ot::ModRM16, Ot::NoOperand), + inst!( 0xF7, 4, 0b0100100000100100, 0x160, NEG , NEG , Ot::ModRM16, Ot::NoOperand), + inst!( 0xF7, 4, 0b0100100000100100, 0x160, MUL , Ot::ModRM16, Ot::NoOperand), + inst!( 0xF7, 4, 0b0100100000100100, 0x160, IMUL , Ot::ModRM16, Ot::NoOperand), + inst!( 0xF7, 4, 0b0100100000100100, 0x160, DIV , Ot::ModRM16, Ot::NoOperand), + inst!( 0xF7, 4, 0b0100100000100100, 0x160, IDIV , Ot::ModRM16, Ot::NoOperand), + // Group + inst!( 0xFE, 5, 0b0000100000100100, 0x020, INC , INC , Ot::ModRM8, Ot::NoOperand), + inst!( 0xFE, 5, 0b0000100000100100, 0x020, DEC , DEC , Ot::ModRM8, Ot::NoOperand), + inst!( 0xFE, 5, 0b0000100000100100, 0x020, CALL , Ot::ModRM8, Ot::NoOperand), + inst!( 0xFE, 5, 0b0000100000100100, 0x020, CALLF , Ot::ModRM8, Ot::NoOperand), + inst!( 0xFE, 5, 0b0000100000100100, 0x020, JMP , Ot::ModRM8, Ot::NoOperand), + inst!( 0xFE, 5, 0b0000100000100100, 0x020, JMPF , Ot::ModRM8, Ot::NoOperand), + inst!( 0xFE, 5, 0b0000100000100100, 0x020, PUSH , Ot::ModRM8, Ot::NoOperand), + inst!( 0xFE, 5, 0b0000100000100100, 0x020, PUSH , Ot::ModRM8, Ot::NoOperand), + // Group + inst!( 0xFF, 5, 0b0000100000100100, 0x026, INC , INC , Ot::ModRM16, Ot::NoOperand), + inst!( 0xFF, 5, 0b0000100000100100, 0x026, DEC , DEC , Ot::ModRM16, Ot::NoOperand), + inst!( 0xFF, 5, 0b0000100000100100, 0x026, CALL , Ot::ModRM16, Ot::NoOperand), + inst!( 0xFF, 5, 0b0000100000100100, 0x026, CALLF , Ot::ModRM16, Ot::NoOperand), + inst!( 0xFF, 5, 0b0000100000100100, 0x026, JMP , Ot::ModRM16, Ot::NoOperand), + inst!( 0xFF, 5, 0b0000100000100100, 0x026, JMPF , Ot::ModRM16, Ot::NoOperand), + inst!( 0xFF, 5, 0b0000100000100100, 0x026, PUSH , Ot::ModRM16, Ot::NoOperand), + inst!( 0xFF, 5, 0b0000100000100100, 0x026, PUSH , Ot::ModRM16, Ot::NoOperand), +]; + +impl NecVx0 { + #[rustfmt::skip] + pub fn decode(bytes: &mut impl ByteQueue, peek: bool) -> Result> { + + let mut operand1_type: OperandType = OperandType::NoOperand; + let mut operand2_type: OperandType = OperandType::NoOperand; + let mut operand1_size: OperandSize = OperandSize::NoOperand; + let mut operand2_size: OperandSize = OperandSize::NoOperand; + + let mut opcode = bytes.q_read_u8(QueueType::First, QueueReader::Biu); + let mut size: u32 = 1; + let mut op_prefixes: u32 = 0; + let mut op_segment_override = None; + let mut decode_idx: usize; + + // Read in opcode prefixes until exhausted + loop { + // Set flags for all prefixes encountered... + op_prefixes |= match opcode { + 0x26 => OPCODE_PREFIX_ES_OVERRIDE, + 0x2E => OPCODE_PREFIX_CS_OVERRIDE, + 0x36 => OPCODE_PREFIX_SS_OVERRIDE, + 0x3E => OPCODE_PREFIX_DS_OVERRIDE, + 0xF0 => OPCODE_PREFIX_LOCK, + 0xF1 => OPCODE_PREFIX_LOCK, + 0xF2 => OPCODE_PREFIX_REP1, + 0xF3 => OPCODE_PREFIX_REP2, + _=> { + break; + } + }; + // ... but only store the last segment override prefix seen + op_segment_override = match opcode { + 0x26 => Some(Segment::ES), + 0x2E => Some(Segment::CS), + 0x36 => Some(Segment::SS), + 0x3E => Some(Segment::DS), + _=> op_segment_override + }; + + // Reading a segment override prefix takes two cycles + bytes.wait(1); + + // Reset first-fetch flag on each prefix read + opcode = bytes.q_read_u8(QueueType::First, QueueReader::Biu); + size += 1; + } + + decode_idx = opcode as usize; + let mut op_lu = &DECODE[decode_idx]; + let mut modrm= ModRmByte::default(); + let mut loaded_modrm = false; + + // Check if resolved first opcode is a group instruction + if op_lu.grp != 0 { + // All group instructions have a modrm w/ op extension. Load the modrm now. + let modrm_len; + (modrm, modrm_len) = ModRmByte::read(bytes); + size += modrm_len; + loaded_modrm = true; + + // Perform secondary lookup of opcode group + extension. + decode_idx = 256 + ((op_lu.grp as usize - 1) * 8) + modrm.get_op_extension() as usize; + op_lu = &DECODE[decode_idx]; + } + + // Set a flag to load the ModRM Byte if either operand requires one + let mut load_modrm = match op_lu.operand1 { + OperandTemplate::ModRM8 => true, + OperandTemplate::ModRM16 => true, + OperandTemplate::Register8 => true, + OperandTemplate::Register16 => true, + _=> false + }; + load_modrm |= match op_lu.operand2 { + OperandTemplate::ModRM8 => true, + OperandTemplate::ModRM16 => true, + OperandTemplate::Register8 => true, + OperandTemplate::Register16 => true, + _=> false + }; + + // Load the ModRM byte if required + if load_modrm && !loaded_modrm { + let modrm_len; + (modrm, modrm_len) = ModRmByte::read(bytes); + size += modrm_len; + loaded_modrm = true; + } + + if loaded_modrm && (!op_lu.gdr.loads_ea()) { + // The EA calculated by the modrm will not be loaded (ie, we proceed to EADONE instead of EALOAD). + // TODO: Move these cycles out of decode + if !matches!(modrm.get_addressing_mode(), AddressingMode::RegisterMode) { + bytes.wait_i(2, &[0x1e3, MC_RTN]); + } + } + + // Resolve operand templates into OperandTypes + let mut match_op = |op_template| -> (OperandType, OperandSize) { + match (op_template, peek) { + (OperandTemplate::ModRM8, _) => { + let addr_mode = modrm.get_addressing_mode(); + let operand_type = match addr_mode { + AddressingMode::RegisterMode => OperandType::Register8(modrm.get_op1_reg8()), + _=> OperandType::AddressingMode(addr_mode), + }; + (operand_type, OperandSize::Operand8) + } + (OperandTemplate::ModRM16, _) => { + let addr_mode = modrm.get_addressing_mode(); + let operand_type = match addr_mode { + AddressingMode::RegisterMode => OperandType::Register16(modrm.get_op1_reg16()), + _=> OperandType::AddressingMode(addr_mode) + }; + (operand_type, OperandSize::Operand16) + } + (OperandTemplate::Register8, _) => { + let operand_type = OperandType::Register8(modrm.get_op2_reg8()); + (operand_type, OperandSize::Operand8) + } + (OperandTemplate::Register16, _) => { + let operand_type = OperandType::Register16(modrm.get_op2_reg16()); + (operand_type, OperandSize::Operand16) + } + (OperandTemplate::SegmentRegister, _) => { + let operand_type = OperandType::Register16(modrm.get_op2_segmentreg16()); + (operand_type, OperandSize::Operand16) + } + (OperandTemplate::Register8Encoded, _) => { + let operand_type = match opcode & OPCODE_REGISTER_SELECT_MASK { + 0x00 => OperandType::Register8(Register8::AL), + 0x01 => OperandType::Register8(Register8::CL), + 0x02 => OperandType::Register8(Register8::DL), + 0x03 => OperandType::Register8(Register8::BL), + 0x04 => OperandType::Register8(Register8::AH), + 0x05 => OperandType::Register8(Register8::CH), + 0x06 => OperandType::Register8(Register8::DH), + 0x07 => OperandType::Register8(Register8::BH), + _ => OperandType::InvalidOperand + }; + (operand_type, OperandSize::Operand8) + } + (OperandTemplate::Register16Encoded, _) => { + let operand_type = match opcode & OPCODE_REGISTER_SELECT_MASK { + 0x00 => OperandType::Register16(Register16::AX), + 0x01 => OperandType::Register16(Register16::CX), + 0x02 => OperandType::Register16(Register16::DX), + 0x03 => OperandType::Register16(Register16::BX), + 0x04 => OperandType::Register16(Register16::SP), + 0x05 => OperandType::Register16(Register16::BP), + 0x06 => OperandType::Register16(Register16::SI), + 0x07 => OperandType::Register16(Register16::DI), + _ => OperandType::InvalidOperand + }; + (operand_type, OperandSize::Operand16) + } + (OperandTemplate::Immediate8, true) => { + // Peek at immediate value now, fetch during execute + let operand = bytes.q_peek_u8(); + size += 1; + (OperandType::Immediate8(operand), OperandSize::Operand8) + } + (OperandTemplate::Immediate8, false) => { + size += 1; + (OperandType::Immediate8(0), OperandSize::Operand8) + } + (OperandTemplate::Immediate16, true) => { + // Peek at immediate value now, fetch during execute + let operand = bytes.q_peek_u16(); + size += 2; + (OperandType::Immediate16(operand), OperandSize::Operand16) + } + (OperandTemplate::Immediate16, false) => { + size += 2; + (OperandType::Immediate16(0), OperandSize::Operand16) + } + (OperandTemplate::Immediate8SignExtended, true) => { + // Peek at immediate value now, fetch during execute + let operand = bytes.q_peek_i8(); + size += 1; + (OperandType::Immediate8s(operand), OperandSize::Operand8) + } + (OperandTemplate::Immediate8SignExtended, false) => { + size += 1; + (OperandType::Immediate8s(0), OperandSize::Operand8) + } + (OperandTemplate::Relative8, true) => { + // Peek at rel8 value now, fetch during execute + let operand = bytes.q_peek_i8(); + size += 1; + (OperandType::Relative8(operand), OperandSize::Operand8) + } + (OperandTemplate::Relative8, false) => { + size += 1; + (OperandType::Relative8(0), OperandSize::Operand8) + } + (OperandTemplate::Relative16, true) => { + // Peek at rel16 value now, fetch during execute + let operand = bytes.q_peek_i16(); + size += 2; + (OperandType::Relative16(operand), OperandSize::Operand16) + } + (OperandTemplate::Relative16, false) => { + size += 2; + (OperandType::Relative16(0), OperandSize::Operand16) + } + (OperandTemplate::Offset8, true) => { + // Peek at offset8 value now, fetch during execute + let operand = bytes.q_peek_u16(); + size += 2; + (OperandType::Offset8(operand), OperandSize::Operand8) + } + (OperandTemplate::Offset8, false) => { + size += 2; + (OperandType::Offset8(0), OperandSize::Operand8) + } + (OperandTemplate::Offset16, true) => { + // Peek at offset16 value now, fetch during execute + let operand = bytes.q_peek_u16(); + size += 2; + (OperandType::Offset16(operand), OperandSize::Operand16) + } + (OperandTemplate::Offset16, false) => { + size += 2; + (OperandType::Offset16(0), OperandSize::Operand16) + } + (OperandTemplate::FixedRegister8(r8), _) => { + (OperandType::Register8(r8), OperandSize::Operand8) + } + (OperandTemplate::FixedRegister16(r16), _) => { + (OperandType::Register16(r16), OperandSize::Operand16) + } + (OperandTemplate::FarAddress, true) => { + let (segment, offset) = bytes.q_peek_farptr16(); + size += 4; + (OperandType::FarAddress(segment,offset), OperandSize::NoSize) + } + (OperandTemplate::FarAddress, false) => { + size += 4; + (OperandType::FarAddress(0,0), OperandSize::NoSize) + } + _ => (OperandType::NoOperand,OperandSize::NoOperand) + } + }; + + if !matches!(op_lu.operand1, OperandTemplate::NoTemplate) { + (operand1_type, operand1_size) = match_op(op_lu.operand1); + } + if !matches!(op_lu.operand2, OperandTemplate::NoTemplate) { + (operand2_type, operand2_size) = match_op(op_lu.operand2); + } + + // Disabled: Decode cannot fail, but this is a placeholder for future error handling in other CPUs + /* + if let Mnemonic::InvalidOpcode = op_lu.mnemonic { + return Err(Box::new(InstructionDecodeError::UnsupportedOpcode(opcode))); + } + */ + + Ok(Instruction { + decode_idx, + opcode, + prefixes: op_prefixes, + address: 0, + size, + mnemonic: op_lu.mnemonic, + segment_override: op_segment_override, + operand1_type, + operand1_size, + operand2_type, + operand2_size + }) + } +} diff --git a/core/src/cpu_vx0/display.rs b/core/src/cpu_vx0/display.rs new file mode 100644 index 00000000..21e697de --- /dev/null +++ b/core/src/cpu_vx0/display.rs @@ -0,0 +1,110 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::display.rs + + Formatting routines for mnemonics and Instruction type. + Converts Instructions into string or token representations. + +*/ + +#[cfg(test)] +mod tests { + #[cfg(feature = "cpu_validator")] + use crate::cpu_validator; + + use crate::{cpu_vx0::*, syntax_token::*}; + + #[test] + fn test_display_methods_match() { + let test_ct = 1_000_000; + + #[cfg(feature = "cpu_validator")] + use cpu_validator::ValidatorMode; + + let mut cpu = Cpu::new( + CpuType::Intel8088, + TraceMode::None, + TraceLogger::None, + #[cfg(feature = "cpu_validator")] + ValidatorType::None, + #[cfg(feature = "cpu_validator")] + TraceLogger::None, + #[cfg(feature = "cpu_validator")] + ValidatorMode::Instruction, + #[cfg(feature = "cpu_validator")] + 1_000_000, + ); + + cpu.randomize_seed(1234); + cpu.randomize_mem(); + + for i in 0..test_ct { + cpu.reset(); + cpu.randomize_regs(); + + if cpu.get_register16(Register16::IP) > 0xFFF0 { + // Avoid IP wrapping issues for now + continue; + } + let opcodes: Vec = (0u8..=255u8).collect(); + + let mut instruction_address = + Cpu::calc_linear_address(cpu.get_register16(Register16::CS), cpu.get_register16(Register16::IP)); + + while (cpu.get_register16(Register16::IP) > 0xFFF0) || ((instruction_address & 0xFFFFF) > 0xFFFF0) { + // Avoid IP wrapping issues for now + cpu.randomize_regs(); + instruction_address = + Cpu::calc_linear_address(cpu.get_register16(Register16::CS), cpu.get_register16(Register16::IP)); + } + + cpu.random_inst_from_opcodes(&opcodes); + + cpu.bus_mut().seek(instruction_address as usize); + let (opcode, _cost) = cpu.bus_mut().read_u8(instruction_address as usize, 0).expect("mem err"); + + let mut i = match Cpu::decode(cpu.bus_mut()) { + Ok(i) => i, + Err(_) => { + log::error!("Instruction decode error, skipping..."); + continue; + } + }; + + let s1 = i.to_string(); + let s2 = SyntaxTokenVec(i.tokenize()).to_string(); + + if s1.to_lowercase() == s2.to_lowercase() { + //log::debug!("Disassembly matches: {}, {}", s1, s2); + } + else { + println!("Test: {} Disassembly mismatch: {}, {}", i, s1, s2); + assert_eq!(s1, s2); + } + } + } +} diff --git a/core/src/cpu_vx0/execute.rs b/core/src/cpu_vx0/execute.rs new file mode 100644 index 00000000..28905a7b --- /dev/null +++ b/core/src/cpu_vx0/execute.rs @@ -0,0 +1,2100 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::execute.rs + + Executes an instruction after it has been fetched. + Includes all main opcode implementations. +*/ + +use crate::{ + cpu_common::{CpuException, ExecutionResult, Mnemonic, OperandType, QueueOp, Segment}, + cpu_vx0::{biu::*, *}, + util, +}; +use ExecutionResult::Okay; + +/* +macro_rules! read_operand { + ($self:ident, $op: expr) => { + { + if $self.i.opcode & 0x01 == 0 { + $self.op1_8 = $self.read_operand8($op, $self.i.segment_override).unwrap() + } + else { + $self.op1_16 = $self.read_operand16($op, $self.i.segment_override).unwrap() + } + } + }; +} + +macro_rules! write_operand { + ($self:ident, $op: expr, $value: expr, $flag: expr ) => { + { + if $self.i.opcode & 0x01 == 0 { + $self.write_operand8($op, $self.i.segment_override, $self.result_8, $flag) + } + else { + $self.write_operand16($op, $self.i.segment_override, $self.result_16, $flag) + } + } + }; +} + +macro_rules! alu_op { + ($self:ident) => { + if $self.i.opcode & 0x01 == 0 { + $self.result_8 = $self.math_op8($self.i.mnemonic, $self.op1_8, $self.op2_8) + } + else { + $self.result_16 = $self.math_op16($self.i.mnemonic, $self.op1_16, $self.op2_16) + } + } +} +*/ + +// rustfmt chokes on large match statements. +#[rustfmt::skip] +impl NecVx0 { + /// Execute the current instruction. At the phase this function is called we have + /// fetched and decoded any prefixes, the opcode byte, modrm and any displacement + /// and populated an Instruction struct. + /// + /// Additionally, if an EA was to be loaded, the load has already been performed. + /// + /// For each opcode, we execute cycles equivalent to the microcode routine for + /// that function. Microcode line numbers are usually provided for cycle tracing. + /// + /// The microcode instruction with a terminating RNI should not be executed, as this + /// requires the next instruction byte to be fetched and is handled by finalize(). + #[rustfmt::skip] + pub fn execute_instruction(&mut self) -> ExecutionResult { + let mut unhandled: bool = false; + let mut jump: bool = false; + let mut exception: CpuException = CpuException::NoException; + + self.step_over_target = None; + + self.trace_comment("EXECUTE"); + + // TODO: Check optimization here. We could reset several flags at once if they were in a + // bitfield. + // Reset instruction reentrancy flag + self.instruction_reentrant = false; + + // Reset jumped flag. + self.jumped = false; + + // Reset trap suppression flag + self.trap_suppressed = false; + + // Decrement trap counters. + self.trap_enable_delay = self.trap_enable_delay.saturating_sub(1); + self.trap_disable_delay = self.trap_disable_delay.saturating_sub(1); + + // If we have an NX loaded RNI cycle from the previous instruction, execute it. + // Otherwise, wait one cycle before beginning instruction if there was no modrm. + if self.nx { + self.trace_comment("RNI"); + self.cycle(); + self.nx = false; + } + else if self.last_queue_op == QueueOp::First { + self.cycle(); + } + + // Check for REPx prefixes + if (self.i.prefixes & OPCODE_PREFIX_REP1 != 0) || (self.i.prefixes & OPCODE_PREFIX_REP2 != 0) { + // A REPx prefix was set + + let mut invalid_rep = false; + + match self.i.mnemonic { + Mnemonic::STOSB | Mnemonic::STOSW | Mnemonic::LODSB | Mnemonic::LODSW | Mnemonic::MOVSB | Mnemonic::MOVSW => { + self.rep_type = RepType::Rep; + } + Mnemonic::SCASB | Mnemonic::SCASW | Mnemonic::CMPSB | Mnemonic::CMPSW => { + // Valid string ops with REP prefix + if self.i.prefixes & OPCODE_PREFIX_REP1 != 0 { + self.rep_type = RepType::Repne; + } + else { + self.rep_type = RepType::Repe; + } + } + Mnemonic::MUL | Mnemonic::IMUL | Mnemonic::DIV | Mnemonic::IDIV => { + // REP prefix on MUL/DIV negates the product/quotient. + self.rep_type = RepType::MulDiv; + } + _ => { + invalid_rep = true; + //return ExecutionResult::ExecutionError( + // format!("REP prefix on invalid opcode: {:?} at [{:04X}:{:04X}].", self.i.mnemonic, self.cs, self.ip) + //); + + /* + log::warn!( + "REP prefix on invalid opcode: {:?} at [{:04X}:{:04X}].", + self.i.mnemonic, + self.cs, + self.ip(), + ); + */ + } + } + + if !invalid_rep { + self.in_rep = true; + self.rep_mnemonic = self.i.mnemonic; + } + } + + // Reset the wait cycle after STI + self.interrupt_inhibit = false; + + // Most instructions will issue an RNI. We can set RNI to false for those that don't. + //self.rni = true; + + // Keep a tally of how many Opcode 0x00's we've executed in a row. Too many likely means we've run + // off the rails into uninitialized memory, whereupon we halt so that we can check things out. + + // This is now optional in the configuration file, as some test applications like acid88 won't work + // otherwise. + if self.i.opcode == 0x00 { + self.opcode0_counter = self.opcode0_counter.wrapping_add(1); + + if self.off_rails_detection && (self.opcode0_counter > 5) { + // Halt permanently by clearing interrupt flag + self.clear_flag(Flag::Interrupt); + self.halted = true; + self.instruction_reentrant = true; + } + } + else { + self.opcode0_counter = 0; + } + + // Main opcode dispatch + match self.i.opcode { + 0x00 | 0x02 | 0x04 | // ADD r/m8, r8 | r8, r/m8 | al, imm8 + 0x08 | 0x0A | 0x0C | // OR r/m8, r8 | r8, r/m8 | al, imm8 + 0x10 | 0x12 | 0x14 | // ADC r/m8, r8 | r8, r/m8 | al, imm8 + 0x18 | 0x1A | 0x1C | // SBB r/m8, r8 | r8, r/m8 | al, imm8 + 0x20 | 0x22 | 0x24 | // AND r/m8, r8 | r8, r/m8 | al, imm8 + 0x28 | 0x2A | 0x2C | // SUB r/m8, r8 | r8, r/m8 | al, imm8 + 0x30 | 0x32 | 0x34 => // XOR r/m8, r8 | r8, r/m8 | al, imm8 + { + // 16 bit ADD variants + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + + self.cycles_nx_i(2, &[0x008, 0x009]); + + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycles_i(2, &[0x009, 0x00a]); + } + + let result = self.math_op8(self.i.mnemonic, op1_value, op2_value); + self.write_operand8(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + } + 0x01 | 0x03 | 0x05 | // ADD r/m16, r16 | r16, r/m16 | ax, imm16 + 0x09 | 0x0B | 0x0D | // OR r/m16, r16 | r16, r/m16 | ax, imm16 + 0x11 | 0x13 | 0x15 | // ADC r/m16, r16 | r16, r/m16 | ax, imm16 + 0x19 | 0x1B | 0x1D | // SBB r/m16, r16 | r16, r/m16 | ax, imm16 + 0x21 | 0x23 | 0x25 | // AND r/m16, r16 | r16, r/m16 | ax, imm16 + 0x29 | 0x2B | 0x2D | // SUB r/m16, r16 | r16, r/m16 | ax, imm16 + 0x31 | 0x33 | 0x35 => // XOR r/m16, r16 | r16, r/m16 | ax, imm16 + { + // 16 bit ADD variants + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + + self.cycles_nx_i(2, &[0x008, 0x009]); + + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycles_i(2, &[0x009, 0x00a]); + } + + let result = self.math_op16(self.i.mnemonic, op1_value, op2_value); + self.write_operand16(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + } + 0x06 => { + // PUSH es + // Flags: None + self.cycles_i(3, &[0x02c, 0x02d, 0x023]); + self.push_register16(Register16::ES, ReadWriteFlag::RNI); + } + 0x07 => { + // POP es + // Flags: None + self.pop_register16(Register16::ES, ReadWriteFlag::RNI); + //self.cycle(); + } + 0x0E => { + // PUSH cs + // Flags: None + self.cycles_i(3, &[0x02c, 0x02d, 0x023]); + self.push_register16(Register16::CS, ReadWriteFlag::RNI); + } + 0x0F => { + // POP cs + // Flags: None + self.pop_register16(Register16::CS, ReadWriteFlag::RNI); + //self.cycle(); + } + 0x16 => { + // PUSH ss + // Flags: None + self.cycles_i(3, &[0x02c, 0x02d, 0x023]); + self.push_register16(Register16::SS, ReadWriteFlag::RNI); + } + 0x17 => { + // POP ss + // Flags: None + self.pop_register16(Register16::SS, ReadWriteFlag::RNI); + //self.cycle(); + } + 0x1E => { + // PUSH ds + // Flags: None + self.cycles_i(3, &[0x02c, 0x02d, 0x023]); + self.push_register16(Register16::DS, ReadWriteFlag::RNI); + } + 0x1F => { + // POP ds + // Flags: None + self.pop_register16(Register16::DS, ReadWriteFlag::RNI); + //self.cycle(); + } + // ES Segment Override Prefix + 0x26 => {} + 0x27 => { + // DAA — Decimal Adjust AL after Addition + self.cycles_nx_i(3, &[0x144, 0x145, 0x146]); + self.daa(); + } + // CS Override Prefix + 0x2E => {} + 0x2F => { + // DAS + self.cycles_nx_i(3, &[0x144, 0x145, 0x146]); + self.das(); + } + // SS Segment Override Prefix + 0x36 => {} + 0x37 => { + // AAA + self.aaa(); + } + 0x38 | 0x3A | 0x3C => { + // CMP r/m8,r8 | r8, r/m8 | al,imm8 + // CMP 8-bit variants + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + + if self.i.opcode == 0x3C { + // 0x018 + self.cycles_nx_i(2, &[MC_JUMP, 0x01a]); + } + else { + // 0x008 + self.cycles_nx_i(2, &[0x008, 0x009]); + } + + let _result = self.math_op8(Mnemonic::CMP, op1_value, op2_value); + //self.write_operand8(self.i.operand1_type, self.i.segment_override, result); + } + 0x39 | 0x3B | 0x3D => { + // CMP r/m16,r16 | r16, r/m16 | ax,imm16 + // CMP 16-bit variants + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + + if self.i.opcode == 0x3D { + // 0x018 + self.cycle_nx_i(0x01a); + } + else { + // 0x008 + self.cycles_nx_i(2, &[0x008, 0x009]); + } + + let _result = self.math_op16(Mnemonic::CMP, op1_value, op2_value); + //self.write_operand16(self.i.operand1_type, self.i.segment_override, result); + } + 0x3E => { + // DS Segment Override Prefix + } + 0x3F => { + // AAS + self.aas(); + } + 0x40..=0x47 => { + // INC r16 register-encoded operands + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + // math_op16 handles flags + let result = self.math_op16(Mnemonic::INC, op1_value, 0); + self.write_operand16(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + + self.cycles_nx(1); + } + 0x48..=0x4F => { + // DEC r16 register-encoded operands + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + // math_op16 handles flags + let result = self.math_op16(Mnemonic::DEC, op1_value, 0); + self.write_operand16(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + + self.cycles_nx(1); + } + 0x50..=0x57 => { + // PUSH reg16 + // Flags: None + let reg = REGISTER16_LUT[(self.i.opcode & 0x07) as usize]; + self.cycles_i(3, &[0x028, 0x029, 0x02a]); + + self.push_register16(reg, ReadWriteFlag::RNI); + } + 0x58..=0x5F => { + // POP reg16 + // Flags: None + let reg = REGISTER16_LUT[(self.i.opcode & 0x07) as usize]; + + self.pop_register16(reg, ReadWriteFlag::RNI); + self.cycle_nx_i(0x035); + } + 0x60 => { + // PUSHA + let saved_sp = self.get_register16(Register16::SP); + self.cycles(2); + self.push_register16(Register16::AX, ReadWriteFlag::Normal); + self.cycles(1); + self.push_register16(Register16::CX, ReadWriteFlag::Normal); + self.push_register16(Register16::DX, ReadWriteFlag::Normal); + self.push_register16(Register16::BX, ReadWriteFlag::Normal); + self.push_u16(saved_sp, ReadWriteFlag::Normal); + self.push_register16(Register16::BP, ReadWriteFlag::Normal); + self.push_register16(Register16::SI, ReadWriteFlag::Normal); + self.push_register16(Register16::DI, ReadWriteFlag::RNI); + } + 0x61..=0x6F => { + + unhandled = true; + } + 0x70..=0x7F => { + // JMP rel8 variants + // Note that 0x60-6F maps to 0x70-7F on 8088 + jump = match self.i.opcode & 0x0F { + 0x00 => self.get_flag(Flag::Overflow), // JO - Jump if overflow set + 0x01 => !self.get_flag(Flag::Overflow), // JNO - Jump it overflow not set + 0x02 => self.get_flag(Flag::Carry), // JB -> Jump if carry set + 0x03 => !self.get_flag(Flag::Carry), // JNB -> Jump if carry not set + 0x04 => self.get_flag(Flag::Zero), // JZ -> Jump if Zero set + 0x05 => !self.get_flag(Flag::Zero), // JNZ -> Jump if Zero not set + 0x06 => self.get_flag(Flag::Carry) || self.get_flag(Flag::Zero), // JBE -> Jump if Carry OR Zero + 0x07 => !self.get_flag(Flag::Carry) && !self.get_flag(Flag::Zero), // JNBE -> Jump if Carry not set AND Zero not set + 0x08 => self.get_flag(Flag::Sign), // JS -> Jump if Sign set + 0x09 => !self.get_flag(Flag::Sign), // JNS -> Jump if Sign not set + 0x0A => self.get_flag(Flag::Parity), // JP -> Jump if Parity set + 0x0B => !self.get_flag(Flag::Parity), // JNP -> Jump if Parity not set + 0x0C => self.get_flag(Flag::Sign) != self.get_flag(Flag::Overflow), // JL -> Jump if Sign flag != Overflow flag + 0x0D => self.get_flag(Flag::Sign) == self.get_flag(Flag::Overflow), // JNL -> Jump if Sign flag == Overflow flag + 0x0E => self.get_flag(Flag::Zero) || (self.get_flag(Flag::Sign) != self.get_flag(Flag::Overflow)), // JLE ((ZF=1) OR (SF!=OF)) + 0x0F => !self.get_flag(Flag::Zero) && (self.get_flag(Flag::Sign) == self.get_flag(Flag::Overflow)), // JNLE ((ZF=0) AND (SF=OF)) + _ => false + }; + + let rel8 = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + self.cycle_i(0x0e9); + + if jump { + self.reljmp2(rel8 as i8 as i16, true); + } + /* + else { + self.cycle_i(0x0ea); + } + */ + } + 0x80 | 0x82 => { + // ADD/OR/ADC/SBB/AND/SUB/XOR/CMP r/m8, imm8 + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + + self.cycle_nx(); + let result = self.math_op8(self.i.mnemonic, op1_value, op2_value); + + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycles_i(2, &[0x00e, 0x00f]); + } + + if self.i.mnemonic != Mnemonic::CMP { + self.write_operand8(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + } + } + 0x81 => { + // ADD/OR/ADC/SBB/AND/SUB/XOR/CMP r/m16, imm16 + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + + self.cycle_nx(); + let result = self.math_op16(self.i.mnemonic, op1_value, op2_value); + + if let OperandType::AddressingMode(_) = self.i.operand1_type { + if self.i.mnemonic != Mnemonic::CMP { + self.cycles_i(2, &[0x00e, 0x00f]); + } + else { + self.cycles_nx_i(2, &[0x00e, 0x00f]); + } + } + + if self.i.mnemonic != Mnemonic::CMP { + self.write_operand16(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + } + } + 0x83 => { + // ADD/ADC/SBB/SUB/CMP r/m16, imm8 (sign-extended) + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + + let sign_extended = op2_value as i8 as i16 as u16; + + // math_op16 handles flags + let result = self.math_op16(self.i.mnemonic, op1_value, sign_extended); + + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycles_i(2, &[0x00e, 0x00f]); + } + + if self.i.mnemonic != Mnemonic::CMP { + self.write_operand16(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + } + } + 0x84 => { + // TEST r/m8, r8 + // Flags: o..sz.pc + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + + self.math_op8(Mnemonic::TEST, op1_value, op2_value); + self.cycles_nx_i(2, &[0x94]); + + /* + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycles_i(2, &[0x95, 0x96]); + } + */ + } + 0x85 => { + // TEST r/m16, r16 + // Flags: o..sz.pc + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + // math_op16 handles flags + self.math_op16(Mnemonic::TEST, op1_value, op2_value); + self.cycles_nx_i(2, &[0x94]); + /* + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycle(); + } + */ + } + 0x86 => { + // XCHG r8, r/m8 + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + + self.cycles_nx(3); + + if let OperandType::AddressingMode(_) = self.i.operand2_type { + // Memory operand takes 2 more cycles + self.cycles(2); + } + + // Exchange values. Write operand2 first so we don't affect EA calculation if EA includes register being swapped. + self.write_operand8(self.i.operand2_type, self.i.segment_override, op1_value, ReadWriteFlag::RNI); + self.write_operand8(self.i.operand1_type, self.i.segment_override, op2_value, ReadWriteFlag::Normal); + } + 0x87 => { + // XCHG r16, r/m16 + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + + self.cycles_nx(3); + + if let OperandType::AddressingMode(_) = self.i.operand2_type { + // Memory operand takes 2 more cycles + self.cycles(2); + } + + // Exchange values. Write operand2 first so we don't affect EA calculation if EA includes register being swapped. + self.write_operand16(self.i.operand2_type, self.i.segment_override, op1_value, ReadWriteFlag::RNI); + self.write_operand16(self.i.operand1_type, self.i.segment_override, op2_value, ReadWriteFlag::Normal); + } + 0x88 | 0x8A => { + // MOV r/m8, r8 | MOV r8, r/m8 + self.cycle_nx(); + let op_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycles_i(2, &[0x000, 0x001]); + } + self.write_operand8(self.i.operand1_type, self.i.segment_override, op_value, ReadWriteFlag::RNI); + } + 0x89 | 0x8B => { + // MOV r/m16, r16 | MOV r16, r/m16 + self.cycle_nx(); + let op_value = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycles_i(2, &[0x000, 0x001]); + } + self.write_operand16(self.i.operand1_type, self.i.segment_override, op_value, ReadWriteFlag::RNI); + } + 0x8C | 0x8E => { + // MOV r/m16, SReg | MOV SReg, r/m16 + + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycle_i(0x0ec); + } + let op_value = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + self.write_operand16(self.i.operand1_type, self.i.segment_override, op_value, ReadWriteFlag::RNI); + } + 0x8D => { + // LEA - Load Effective Address + let ea = self.load_effective_address(self.i.operand2_type); + match ea { + Some(value) => { + self.write_operand16(self.i.operand1_type, None, value, ReadWriteFlag::RNI); + } + None => { + // In the event of an invalid (Register) operand2, operand1 is set to the last EA calculated by an instruction. + self.write_operand16(self.i.operand1_type, None, self.last_ea, ReadWriteFlag::RNI); + //self.cycles(1); + } + } + } + 0x8F => { + // POP r/m16 + self.cycle_i(0x040); + let value = self.pop_u16(); + self.cycle_i(0x042); + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycles_i(2, &[0x043, 0x044]); + } + self.write_operand16(self.i.operand1_type, self.i.segment_override, value, ReadWriteFlag::RNI); + } + 0x90..=0x97 => { + // XCHG AX, r + // Cycles: 3 (1 Fetch + 2 EU) + // Flags: None + let op_reg = REGISTER16_LUT[(self.i.opcode & 0x07) as usize]; + let ax_value = self.a.x(); + let op_reg_value = self.get_register16(op_reg); + + self.cycles_nx_i(2, &[0x084, 0x085]); + + self.set_register16(Register16::AX, op_reg_value); + self.set_register16(op_reg, ax_value); + } + 0x98 => { + // CBW - Convert Byte to Word + // Flags: None + if self.a.l() & 0x80 != 0 { + self.a.set_h(0xFF); + } + else { + self.a.set_h(0); + } + } + 0x99 => { + // CWD - Convert Word to Doubleword + // Flags: None + self.cycles(3); + if self.a.x() & 0x8000 == 0 { + self.d.set_x(0x0000); + } + else { + self.cycle(); // Microcode jump @ 05a + self.d.set_x(0xFFFF); + } + } + 0x9A => { + // CALLF - Call Far addr16:16 + // This instruction reads a direct FAR address from the instruction queue. (See 0xEA for its twin JMPF) + let (segment, offset) = self.read_operand_faraddr(); + self.farcall(segment, offset, true); + + // Save next address if we step over this CALL. + self.step_over_target = Some(CpuAddress::Segmented(self.cs, self.ip())); + + self.push_call_stack( + CallStackEntry::CallF { + ret_cs: self.cs, + ret_ip: self.ip(), + call_cs: segment, + call_ip: offset + }, + self.cs, + self.ip(), + ); + + /* + self.cs = segment; + self.ip = offset; + + // NEARCALL + self.biu_queue_flush(); + self.cycles_i(3, &[0x077, 0x078, 0x079]); + self.push_u16(next_i, ReadWriteFlag::RNI); + */ + jump = true; + } + 0x9B => { + // WAIT + self.cycles(3); + } + 0x9C => { + // PUSHF - Push Flags + self.cycles(3); + self.push_flags(ReadWriteFlag::RNI); + } + 0x9D => { + // POPF - Pop Flags + self.pop_flags(); + } + 0x9E => { + // SAHF - Store AH into Flags + self.store_flags(self.a.h() as u16); + } + 0x9F => { + // LAHF - Load Status Flags into AH Register + let flags = self.load_flags() as u8; + self.set_register8(Register8::AH, flags); + } + 0xA0 => { + // MOV al, offset8 + // These MOV variants are unique in that they take a direct offset with no modr/m byte + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + //self.cycle_i(0x063); + self.set_register8(Register8::AL, op2_value); + } + 0xA1 => { + // MOV AX, offset16 + // These MOV variants are unique in that they take a direct offset with no modr/m byte + let op2_value = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + //self.cycle_i(0x063); + self.set_register16(Register16::AX, op2_value); + } + 0xA2 => { + // MOV offset8, Al + // These MOV variants are unique in that they take a direct offset with no modr/m byte + let op2_value = self.a.l(); + self.write_operand8(self.i.operand1_type, self.i.segment_override, op2_value, ReadWriteFlag::RNI); + } + 0xA3 => { + // MOV offset16, AX + // These MOV variants are unique in that they take a direct offset with no modr/m byte + let op2_value = self.a.x(); + self.write_operand16(self.i.operand1_type, self.i.segment_override, op2_value, ReadWriteFlag::RNI); + } + 0xA4 | 0xA5 => { + // MOVSB & MOVSW + + // rep_start() will terminate early if CX==0 + if self.rep_start() { + + self.string_op(self.i.mnemonic, self.i.segment_override); + self.cycle_i(0x130); + + // Check for end condition (CX==0) + if self.in_rep { + + self.decrement_register16(Register16::CX); // 131 + + // Check for interrupt + if self.intr_pending { + self.cycles_i(2, &[0x131, MC_JUMP]); // Jump to RPTI + self.rep_interrupt(); + + } + else { + self.cycles_i(2, &[0x131, 0x132]); + + if self.c.x() == 0 { + // Fall through to 133, RNI + self.rep_end(); + } + else { + self.cycle_i(MC_JUMP); // jump to 1 + } + } + } + else { + // End non-rep prefixed MOVSB + self.cycle_i(MC_JUMP); // jump to 133, RNI + } + } + } + 0xA6 | 0xA7 | 0xAE | 0xAF => { + // CMPSB, CMPSW, SCASB, SCASW + // Segment override: DS overridable + // Flags: All + + if self.rep_start() { + + self.string_op(self.i.mnemonic, self.i.segment_override); + + if self.in_rep { + let mut end = false; + // Check for REP end condition #1 (Z/NZ) + self.cycle_i(0x129); + self.decrement_register16(Register16::CX); // 129 + + match self.rep_type { + RepType::Repne => { + // Repeat while NOT zero. If Zero flag is set, end REP. + if self.get_flag(Flag::Zero) { + self.rep_end(); + self.cycle_i(MC_JUMP); // Jump to 1f4, RNI + end = true; + } + } + RepType::Repe => { + // Repeat while zero. If zero flag is NOT set, end REP. + if !self.get_flag(Flag::Zero) { + self.rep_end(); + self.cycle_i(MC_JUMP); // Jump to 1f4, RNI + end = true; + } + } + _=> {} + }; + + if !end { + + self.cycle_i(0x12a); + + if self.intr_pending { + self.cycle_i(MC_JUMP); // Jump to RPTI + self.rep_interrupt(); + } + + // Check for REP end condition #2 (CX==0) + self.cycle_i(0x12b); + if self.c.x() == 0 { + self.rep_end(); + // Next instruction is 1f4: RNI, so don't spend cycle + } + else { + self.cycle_i(MC_JUMP); // Jump to line 1: 121 + } + } + } + else { + // End non-rep prefixed CMPS + self.cycle_i(MC_JUMP); // Jump to 1f4, RNI + } + } + } + 0xA8 => { + // TEST al, imm8 + // Flags: o..sz.pc + let op1_value = self.a.l(); + let op2_value = self.read_operand8(self.i.operand2_type, None).unwrap(); + + self.math_op8(Mnemonic::TEST, op1_value, op2_value); + } + 0xA9 => { + // TEST ax, imm16 + // Flags: o..sz.pc + let op1_value = self.a.x(); + let op2_value = self.read_operand16(self.i.operand2_type, None).unwrap(); + + self.math_op16(Mnemonic::TEST, op1_value, op2_value); + } + 0xAA | 0xAB => { + // STOSB & STOSW + + if self.rep_start() { + + self.string_op(self.i.mnemonic, None); + self.cycle_i(0x11e); + + // Check for end condition (CX==0) + if self.in_rep { + + // Check for interrupt + self.cycle_i(0x11f); + if self.intr_pending { + self.cycle_i(MC_JUMP); // Jump to RPTI + self.rep_interrupt(); + } + + self.cycle_i(0x1f0); + self.decrement_register16(Register16::CX); //1f0 + if self.c.x() == 0 { + self.rep_end(); + } + else { + // Jump to 1 + self.cycle_i(MC_JUMP); + } + } + else { + // Jump to 1f1 + self.cycle_i(MC_JUMP); + } + } + } + 0xAC | 0xAD => { + // LODSB & LODSW + // Flags: None + // Although LODSx is not typically used with a REP prefix, it can be + + // rep_start() will terminate early if CX==0 + if self.rep_start() { + + self.string_op(self.i.mnemonic, self.i.segment_override); + self.cycles_i(3, &[0x12e, MC_JUMP, 0x1f8]); + + // Check for REP end condition #1 (CX==0) + if self.in_rep { + + self.cycles_i(2, &[MC_JUMP, 0x131]); // Jump to 131 + self.decrement_register16(Register16::CX); // 131 + + // Check for interrupt + if self.intr_pending { + self.cycle_i(MC_JUMP); // Jump to RPTI + self.rep_interrupt(); + } + else { + self.cycle_i(0x132); + + if self.c.x() == 0 { + // Fall through to 133/1f9, RNI + self.rep_end(); + } + else { + self.cycle_i(MC_JUMP); // jump to 1 + } + } + } + // Non-prefixed LODSx ends with RNI + } + } + 0xB0..=0xB7 => { + // MOV r8, imm8 + let op2_value = self.read_operand8(self.i.operand2_type, None).unwrap(); + if let OperandType::Register8(reg) = self.i.operand1_type { + self.set_register8(reg, op2_value); + } + self.cycle_i(MC_JUMP); + } + 0xB8..=0xBF => { + // MOV r16, imm16 + let op2_value = self.read_operand16(self.i.operand2_type, None).unwrap(); + if let OperandType::Register16(reg) = self.i.operand1_type { + self.set_register16(reg, op2_value); + } + //self.cycle_i(0x01e); + } + 0xC0 | 0xC2 => { + // RETN imm16 - Return from call w/ release + // 0xC0 undocumented alias for 0xC2 + // Flags: None + + let stack_disp = self.read_operand16(self.i.operand1_type, None).unwrap(); + self.cycle_i(MC_JUMP); // JMP to FARRET + let new_pc = self.pop_u16(); + self.pc = new_pc; + + self.biu_fetch_suspend(); + self.cycles_i(2, &[0x0c3, 0x0c4]); + self.biu_queue_flush(); + self.cycles_i(3, &[0x0c5, MC_JUMP, 0x0ce]); + + self.release(stack_disp); + + // Pop call stack + //self.call_stack.pop_back(); + + jump = true + } + 0xC1 | 0xC3 => { + // RETN - Return from call + // 0xC1 undocumented alias for 0xC3 + // Flags: None + // Effectively, this instruction is pop ip + let new_pc = self.pop_u16(); + self.pc = new_pc; + self.biu_fetch_suspend(); + self.cycle_i(0x0bd); + self.biu_queue_flush(); + self.cycles_i(2, &[0x0be, 0x0bf]); + + // Pop call stack + // self.call_stack.pop_back(); + + jump = true + } + 0xC4 => { + // LES - Load ES from Pointer + self.cycles_i(2, &[0x0F0, 0x0F1]); + + // Operand 2 is far pointer + let (les_segment, les_offset) = + self.read_operand_farptr( + self.i.operand2_type, + self.i.segment_override, + ReadWriteFlag::Normal + ).unwrap(); + + //log::trace!("LES instruction: Loaded {:04X}:{:04X}", les_segment, les_offset); + self.write_operand16( + self.i.operand1_type, + self.i.segment_override, + les_offset, + ReadWriteFlag::Normal); + self.es = les_segment; + } + 0xC5 => { + // LDS - Load DS from Pointer + self.cycles_i(2, &[0x0F4, 0x0F5]); + + // Operand 2 is far pointer + let (lds_segment, lds_offset) = + self.read_operand_farptr( + self.i.operand2_type, + self.i.segment_override, + ReadWriteFlag::RNI + ).unwrap(); + + //log::trace!("LDS instruction: Loaded {:04X}:{:04X}", lds_segment, lds_offset); + self.write_operand16( + self.i.operand1_type, + self.i.segment_override, + lds_offset, + ReadWriteFlag::Normal); + self.ds = lds_segment; + //self.cycle_i(0x0f7); + } + 0xC6 => { + // MOV r/m8, imm8 + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + self.cycles(2); + self.write_operand8(self.i.operand1_type, self.i.segment_override, op2_value, ReadWriteFlag::RNI); + } + 0xC7 => { + // MOV r/m16, imm16 + let op2_value = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + self.cycle_i(0x01e); + self.write_operand16(self.i.operand1_type, self.i.segment_override, op2_value, ReadWriteFlag::RNI); + } + 0xC8 | 0xCA => { + // RETF imm16 - Far Return w/ release + // 0xC8 undocumented alias for 0xCA + let stack_disp = self.read_operand16(self.i.operand1_type, None).unwrap(); + self.farret(true); + self.release(stack_disp); + self.cycle_i(0x0ce); + jump = true; + } + 0xC9 | 0xCB => { + // RETF - Far Return + // 0xC9 undocumented alias for 0xCB + self.cycle_i(0x0c0); + self.farret(true); + jump = true; + } + 0xCC => { + // INT 3 - Software Interrupt 3 + // This is a special form of INT which assumes IRQ 3 always. Most assemblers will not generate this form + + // Save next address if we step over this INT. + self.step_over_target = Some(CpuAddress::Segmented(self.cs, self.ip())); + + self.int3(); + jump = true; + } + 0xCD => { + // INT imm8 - Software Interrupt + // The Interrupt flag does not affect the handling of non-maskable interrupts (NMIs) or software interrupts + // generated by the INT instruction. + + // Get interrupt number (immediate operand) + let irq = self.read_operand8(self.i.operand1_type, None).unwrap(); + + // Save next address if we step over this INT. + self.step_over_target = Some(CpuAddress::Segmented(self.cs, self.ip())); + + self.cycle_i(MC_JUMP); // Jump to INTR + self.sw_interrupt(irq); + jump = true; + } + 0xCE => { + // INTO - Call Overflow Interrupt Handler + if self.get_flag(Flag::Overflow) { + self.cycles_i(4, &[0x1ac, 0x1ad, MC_JUMP, 0x1af]); + + // Save next address if we step over this INT. + self.step_over_target = Some(CpuAddress::Segmented(self.cs, self.ip())); + self.sw_interrupt(4); + jump = true; + } + else { + // Overflow not set. + self.cycles_i(2, &[0x1ac, 0x1ad]); + } + } + 0xCF => { + // IRET instruction + self.iret_routine(); + jump = true; + } + 0xD0 => { + // ROL, ROR, RCL, RCR, SHL, SHR, SAR: r/m8, 0x01 + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + let result = self.bitshift_op8(self.i.mnemonic, op1_value, 1); + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycle_i(0x088); + } + self.write_operand8(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + } + 0xD1 => { + // ROL, ROR, RCL, RCR, SHL, SHR, SAR: r/m16, 0x01 + + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let result = self.bitshift_op16(self.i.mnemonic, op1_value, 1); + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycle_i(0x088); + } + self.write_operand16(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + } + 0xD2 => { + // ROL, ROR, RCL, RCR, SHL, SHR, SAR: r/m8, cl + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + + self.cycles_i(6, &[0x08c, 0x08d, 0x08e, MC_JUMP, 0x090, 0x091]); + //self.cycles_i(5, &[0x08d, 0x08e, MC_JUMP, 0x090, 0x091]); + + if self.c.l() > 0 { + for _ in 0..(self.c.l() ) { + self.cycles_i(4, &[MC_JUMP, 0x08f, 0x090, 0x091]); + } + } + + // If there is a terminal write to M, don't process RNI on line 0x92 + if let OperandType::AddressingMode(_) = self.i.operand1_type { + //if self.c.l() != 0 { + self.cycle_i(0x092); + //} + } + + let result = self.bitshift_op8(self.i.mnemonic, op1_value, op2_value); + + self.write_operand8(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + } + 0xD3 => { + // ROL, ROR, RCL, RCR, SHL, SHR, SAR: r/m16, cl + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + + self.cycles_i(6, &[0x08c, 0x08d, 0x08e, MC_JUMP, 0x090, 0x091]); + //self.cycles_i(5, &[0x08d, 0x08e, MC_JUMP, 0x090, 0x091]); + + if self.c.l() > 0 { + for _ in 0..(self.c.l() ) { + self.cycles_i(4, &[MC_JUMP, 0x08f, 0x090, 0x091]); + } + } + + // If there is a terminal write to M, don't process RNI on line 0x92 + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycle_i(0x092); + } + + let result = self.bitshift_op16(self.i.mnemonic, op1_value, op2_value); + + self.write_operand16(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + } + 0xD4 => { + // AAM - Ascii adjust AX after Multiply + // Get imm8 value + let op1_value = self.read_operand8(self.i.operand1_type, None).unwrap(); + + if !self.aam(op1_value) { + self.set_szp_flags_from_result_u8(0); + self.clear_flag(Flag::AuxCarry); + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.int0(); + jump = true; + exception = CpuException::DivideError; + } + } + 0xD5 => { + // AAD - Ascii Adjust before Division + let op1_value = self.read_operand8(self.i.operand1_type, None).unwrap(); + self.aad(op1_value); + } + 0xD6 => { + // SALC - Undocumented Opcode - Set Carry flag in AL + // http://www.rcollins.org/secrets/opcodes/SALC.html + + self.set_register8(Register8::AL, + match self.get_flag(Flag::Carry) { + true => 0xFF, + false => 0 + } + ); + } + 0xD7 => { + // XLAT + + // Handle segment override, default DS + let segment = self.i.segment_override.unwrap_or(Segment::DS); + let disp16: u16 = self.b.x().wrapping_add(self.a.l() as u16); + + self.cycles_i(3, &[0x10c, 0x10d, 0x10e]); + + let value = self.biu_read_u8(segment, disp16); + + self.set_register8(Register8::AL, value); + } + 0xD8..=0xDF => { + // ESC - FPU instructions. + + // Perform dummy read if memory operand + let _op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override); + } + 0xE0 | 0xE1 => { + // LOOPNE & LOOPE + // LOOPNE - Decrement CX, Jump short if count!=0 and ZF=0 + // LOOPE - Jump short if count!=0 and ZF=1 + // loop does not modify flags + + self.decrement_register16(Register16::CX); + self.cycles_i(2, &[0x138, 0x139]); + + let mut zero_condition = !self.get_flag(Flag::Zero); + if self.i.opcode == 0xE1 { + zero_condition = !zero_condition; + } + + let rel8 = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + + if self.c.x() != 0 && zero_condition { + self.reljmp2(rel8 as i8 as i16, true); + jump = true; + } + else { + self.cycle_i(0x13c); + } + } + 0xE2 => { + // LOOP - Jump short if count != 0 + // loop does not modify flags + + self.decrement_register16(Register16::CX); + self.cycles_i(2, &[0x140, 0x141]); + + let rel8 = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + + if self.c.x() != 0 { + self.reljmp2(rel8 as i8 as i16, true); + jump = true; + } + if !jump { + self.cycle(); + } + } + 0xE3 => { + // JCXZ - Jump if CX == 0 + // Flags: None + + self.cycles_i(2, &[0x138, 0x139]); + let rel8 = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + + self.cycle_i(0x13b); + + if self.c.x() == 0 { + self.reljmp2(rel8 as i8 as i16, true); + jump = true; + } + else { + self.cycle_i(0x13c); + } + } + 0xE4 => { + // IN al, imm8 + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + self.cycles_i(2, &[0x0ad, 0x0ae]); + + let in_byte = self.biu_io_read_u8(op2_value as u16); + self.set_register8(Register8::AL, in_byte); + //println!("IN: Would input value from port {:#02X}", op2_value); + } + 0xE5 => { + // IN ax, imm8 + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + self.cycles_i(2, &[0x0ad, 0x0ae]); + + let in_word = self.biu_io_read_u16(op2_value as u16, ReadWriteFlag::Normal); + self.set_register16(Register16::AX, in_word); + } + 0xE6 => { + // OUT imm8, al + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + self.cycles_i(2, &[0x0b1, 0x0b2]); + + // Write to port + self.biu_io_write_u8(op1_value as u16, op2_value, ReadWriteFlag::RNI); + } + 0xE7 => { + // OUT imm8, ax + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + self.cycles_i(2, &[0x0b1, 0x0b2]); + + // Write to consecutive ports + self.biu_io_write_u16(op1_value as u16, op2_value, ReadWriteFlag::RNI); + + /* + // Write first 8 bits to first port + self.biu_io_write_u8(op1_value as u16, (op2_value & 0xFF) as u8, ReadWriteFlag::Normal); + // Write next 8 bits to port + 1 + self.biu_io_write_u8((op1_value + 1) as u16, (op2_value >> 8 & 0xFF) as u8, ReadWriteFlag::RNI); + */ + } + 0xE8 => { + // CALL rel16 + // Unique microcode routine. Does not call NEARCALL. + + // Fetch rel16 operand + let rel16 = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + + self.biu_fetch_suspend(); // 0x07E + self.cycles_i(2, &[0x07e, 0x07f]); + self.corr(); + self.cycle_i(0x080); + + // Save next address if we step over this CALL. + self.step_over_target = Some(CpuAddress::Segmented(self.cs, self.pc)); + + let ret_addr = self.pc; + // Add rel16 to pc + let new_pc = util::relative_offset_u16(self.pc, rel16 as i16); + + // Add to call stack + self.push_call_stack( + CallStackEntry::Call { + ret_cs: self.cs, + ret_ip: self.pc, + call_ip: new_pc + }, + self.cs, + self.pc + ); + + // Set new IP + self.pc = new_pc; + self.biu_queue_flush(); + self.cycles_i(3, &[0x081, 0x082, MC_JUMP]); + + // Push return address + self.push_u16(ret_addr, ReadWriteFlag::RNI); + jump = true; + } + 0xE9 => { + // JMP rel16 + let rel16 = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + + // We fall through to reljmp, so no jump + self.reljmp2(rel16 as i16, false); + jump = true; + } + 0xEA => { + // JMP FAR [addr16:16] + // This instruction reads a direct FAR address from the instruction queue. (See 0x9A for its twin CALLF) + let (segment, offset) = self.read_operand_faraddr(); + self.biu_fetch_suspend(); + self.cycles_i(2, &[0x0e4, 0x0e5]); + self.cs = segment; + self.pc = offset; + self.biu_queue_flush(); + self.cycle_i(0x0e6); // Doesn't hurt to run this RNI as we have to re-fill queue + jump = true; + } + 0xEB => { + // JMP rel8 + let rel8 = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + self.reljmp2(rel8 as i8 as i16, true); // We jump directly into reljmp + jump = true + } + 0xEC => { + // IN al, dx + let op2_value = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + let in_byte = self.biu_io_read_u8(op2_value); + self.set_register8(Register8::AL, in_byte); + } + 0xED => { + // IN ax, dx + let op2_value = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + let in_word = self.biu_io_read_u16(op2_value, ReadWriteFlag::Normal); + self.set_register16(Register16::AX, in_word); + } + 0xEE => { + // OUT dx, al + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + self.cycle_i(0x0b8); + + self.biu_io_write_u8(op1_value, op2_value, ReadWriteFlag::RNI); + } + 0xEF => { + // OUT dx, ax + // On the 8088, this does two writes to successive port #'s + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + self.cycle_i(0x0b8); + + if op1_value == 0x06 { + log::debug!("OUT of {:02X}", op2_value); + } + + // Write to consecutive ports + self.biu_io_write_u16(op1_value, op2_value, ReadWriteFlag::RNI); + + /* + // Write first 8 bits to first port + self.biu_io_write_u8(op1_value, (op2_value & 0xFF) as u8, ReadWriteFlag::Normal); + // Write next 8 bits to port + 1 + self.biu_io_write_u8(op1_value + 1, (op2_value >> 8 & 0xFF) as u8, ReadWriteFlag::RNI); + */ + } + 0xF0 => { + unhandled = true; + } + 0xF1 => { + // Does nothing? + self.cycle(); + } + 0xF2 => { + unhandled = true; + } + 0xF3 => { + unhandled = true; + } + 0xF4 => { + // HLT - Halt + // HLT is non-microcoded, so cycles spent here aren't logged by mc. + + self.biu_bus_wait_halt(); // wait until at least t2 of m-cycle + self.halt_not_hold = true; // set internal halt signal + self.biu_fetch_halt(); // halt prefetcher + self.biu_bus_wait_finish(); // wait until end of m-cycle + + if self.intr { + // If an intr is pending now, execute it without actually halting. + log::trace!("Halt overriden at [{:05X}]", NecVx0::calc_linear_address(self.cs, self.ip())); + self.cycles(2); // Cycle to load interrupt routine + self.halt_not_hold = false; + } + else { + // Actually halt + log::trace!("Halt at [{:05X}]", NecVx0::calc_linear_address(self.cs, self.ip())); + self.halted = true; + self.biu_halt(); + // HLT is reentrant as step will remain in halt state until interrupt, even + // when stepped. + self.instruction_reentrant = true; + } + + } + 0xF5 => { + // CMC - Complement (invert) Carry Flag + let carry_flag = self.get_flag(Flag::Carry); + self.set_flag_state(Flag::Carry, !carry_flag); + } + 0xF6 => { + // Miscellaneous Opcode Extensions, r/m8, imm8 + + // REP negates product/quotient of MUL/DIV + let negate = (self.i.prefixes & (OPCODE_PREFIX_REP1 | OPCODE_PREFIX_REP2)) != 0; + + match self.i.mnemonic { + + Mnemonic::TEST => { + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + + // 8 bit TEST takes a jump + self.cycles_i(2, &[MC_JUMP, 0x09a]); + + // Don't use result, just set flags + let _result = self.math_op8(self.i.mnemonic, op1_value, op2_value); + } + Mnemonic::NOT => { + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + let result = self.math_op8(self.i.mnemonic, op1_value, 0); + + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycles_i(2,&[0x04c, 0x04d]); + } + self.write_operand8(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + } + Mnemonic::NEG => { + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + let result = self.math_op8(self.i.mnemonic, op1_value, 0); + + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycles_i(2,&[0x050, 0x051]); + } + self.write_operand8(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + } + Mnemonic::MUL => { + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + + //self.multiply_u8(op1_value); + let product = self.mul8(self.a.l(), op1_value, false, negate); + self.set_register16(Register16::AX, product); + + if let OperandType::Register8(_) = self.i.operand1_type { + self.cycle(); + } + + self.set_szp_flags_from_result_u8(self.a.h()); + } + Mnemonic::IMUL => { + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + + //self.multiply_i8(op1_value as i8); + let product = self.mul8(self.a.l(), op1_value, true, negate); + self.set_register16(Register16::AX, product); + + if let OperandType::Register8(_) = self.i.operand1_type { + self.cycle(); + } + + self.set_szp_flags_from_result_u8(self.a.h()); + } + Mnemonic::DIV => { + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + if let OperandType::Register8(_) = self.i.operand1_type { + self.cycle(); + } + + match self.div8(self.a.x(), op1_value, false, negate) { + Ok((al, ah)) => { + self.set_register8(Register8::AL, al); // Quotient in AL + self.set_register8(Register8::AH, ah); // Remainder in AH + } + Err(_) => { + + self.set_szp_flags_from_result_u8(self.a.h()); + //self.set_flag(Flag::Zero); + //self.clear_flag(Flag::Sign); + self.clear_flag(Flag::AuxCarry); + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.int0(); + exception = CpuException::DivideError; + } + } + } + Mnemonic::IDIV => { + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + if let OperandType::Register8(_) = self.i.operand1_type { + self.cycle(); + } + + match self.div8(self.a.x(), op1_value, true, negate) { + Ok((al, ah)) => { + self.set_register8(Register8::AL, al); // Quotient in AL + self.set_register8(Register8::AH, ah); // Remainder in AH + } + Err(_) => { + + self.set_szp_flags_from_result_u8(self.a.h()); + //self.set_flag(Flag::Zero); + //self.clear_flag(Flag::Sign); + self.clear_flag(Flag::AuxCarry); + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + + // Don't include REP prefix as part of instruction size + //let size_adj = if self.i.prefixes & (OPCODE_PREFIX_REP1 | OPCODE_PREFIX_REP2) != 0 { 1 } else { 0 }; + self.int0(); + exception = CpuException::DivideError; + } + } + } + _=> unhandled = true + } + } + 0xF7 => { + // Miscellaneous Opcode Extensions, r/m16, imm16 + + // REP negates product/quotient of MUL/DIV + let negate = (self.i.prefixes & (OPCODE_PREFIX_REP1 | OPCODE_PREFIX_REP2)) != 0; + + match self.i.mnemonic { + + Mnemonic::TEST => { + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + + self.cycle_i(0x09a); + // Don't use result, just set flags + let _result = self.math_op16(self.i.mnemonic, op1_value, op2_value); + } + Mnemonic::NOT => { + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let result = self.math_op16(self.i.mnemonic, op1_value, 0); + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycles_i(2,&[0x04c, 0x04d]); + } + self.write_operand16(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + } + Mnemonic::NEG => { + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let result = self.math_op16(self.i.mnemonic, op1_value, 0); + + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycles_i(2,&[0x050, 0x051]); + } + self.write_operand16(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + } + Mnemonic::MUL => { + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + // Multiply handles writing to ax + //self.multiply_u16(op1_value); + + let (dx, ax) = self.mul16(self.a.x(), op1_value, false, negate); + + if let OperandType::Register16(_) = self.i.operand1_type { + self.cycle(); + } + + //self.cycle(); + self.set_register16(Register16::DX, dx); + self.set_register16(Register16::AX, ax); + + self.set_szp_flags_from_result_u16(self.d.x()); + } + Mnemonic::IMUL => { + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + // Multiply handles writing to dx:ax + //self.multiply_i16(op1_value as i16); + + let (dx, ax) = self.mul16(self.a.x(), op1_value, true, negate); + + if let OperandType::Register16(_) = self.i.operand1_type { + self.cycle(); + } + + self.set_register16(Register16::DX, dx); + self.set_register16(Register16::AX, ax); + + self.set_szp_flags_from_result_u16(self.d.x()); + } + Mnemonic::DIV => { + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + if let OperandType::Register16(_) = self.i.operand1_type { + self.cycle(); + } + + match self.div16(((self.d.x() as u32) << 16 ) | (self.a.x() as u32), op1_value, false, negate) { + Ok((quotient, remainder)) => { + self.set_register16(Register16::AX, quotient); // Quotient in AX + self.set_register16(Register16::DX, remainder); // Remainder in DX + } + Err(_) => { + + self.set_szp_flags_from_result_u8(self.a.h()); + //self.set_flag(Flag::Zero); + //self.clear_flag(Flag::Sign); + self.clear_flag(Flag::AuxCarry); + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.int0(); + + exception = CpuException::DivideError; + } + } + } + Mnemonic::IDIV => { + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + if let OperandType::Register16(_) = self.i.operand1_type { + self.cycle(); + } + + match self.div16(((self.d.x() as u32) << 16 ) | (self.a.x() as u32), op1_value, true, negate) { + Ok((quotient, remainder)) => { + self.set_register16(Register16::AX, quotient); // Quotient in AX + self.set_register16(Register16::DX, remainder); // Remainder in DX + } + Err(_) => { + + self.set_szp_flags_from_result_u8(self.a.h()); + //self.set_flag(Flag::Zero); + //self.clear_flag(Flag::Sign); + self.clear_flag(Flag::AuxCarry); + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.int0(); + exception = CpuException::DivideError; + } + } + } + _=> unhandled = true + } + } + 0xF8 => { + // CLC - Clear Carry Flag + self.clear_flag(Flag::Carry); + //self.cycle() + } + 0xF9 => { + // STC - Set Carry Flag + self.set_flag(Flag::Carry); + //self.cycle() + } + 0xFA => { + // CLI - Clear Interrupt Flag + self.clear_flag(Flag::Interrupt); + //self.cycle() + } + 0xFB => { + // STI - Set Interrupt Flag + self.set_flag(Flag::Interrupt); + //self.cycle() + } + 0xFC => { + // CLD - Clear Direction Flag + self.clear_flag(Flag::Direction); + //self.cycle() + } + 0xFD => { + // STD = Set Direction Flag + self.set_flag(Flag::Direction); + //self.cycle() + } + 0xFE => { + // INC/DEC r/m8 + // Technically only the INC and DEC forms of this group are valid. However, the other operands do 8 bit + // sorta-broken versions of CALL, JMP and PUSH. The behavior implemented here was derived from + // experimentation with a real 8088 CPU. + match self.i.mnemonic { + // INC/DEC r/m16 + Mnemonic::INC | Mnemonic::DEC => { + let op_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + let result = self.math_op8(self.i.mnemonic, op_value, 0); + + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycles_i(2,&[0x020, 0x021]); + } + self.write_operand8(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + }, + // Call Near + Mnemonic::CALL => { + + if let OperandType::AddressingMode(_) = self.i.operand1_type { + // Reads only 8 bit operand from modrm. + let ptr8 = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + + // Push only 8 bits of next IP onto stack + let next_i = self.ip(); + + // We do not allow stepping over 0xFE call here as it is unlikely to lead to a valid location or return. + + self.push_u8((next_i & 0xFF) as u8, ReadWriteFlag::Normal); + + // temporary timings + self.biu_fetch_suspend(); + self.cycles(4); + self.biu_queue_flush(); + + // Set only lower 8 bits of IP, upper bits FF + self.pc = 0xFF00 | ptr8 as u16; + } + else if let OperandType::Register8(reg) = self.i.operand1_type { + + // Push only 8 bits of next IP onto stack + let next_i = self.ip() + (self.i.size as u16); + self.push_u8((next_i & 0xFF) as u8, ReadWriteFlag::Normal); + + // temporary timings + self.biu_fetch_suspend(); + self.cycles(4); + self.biu_queue_flush(); + + // If this form uses a register operand, the full 16 bits are copied to IP. + self.pc = self.get_register16(NecVx0::reg8to16(reg)); + } + jump = true; + } + // Call Far + Mnemonic::CALLF => { + if let OperandType::AddressingMode(mode) = self.i.operand1_type { + let (ea_segment, ea_offset) = self.calc_effective_address(mode, None); + + // Read one byte of offset and one byte of segment + let offset = self.biu_read_u8(ea_segment, ea_offset); + + self.cycles_i(3, &[0x1e2, MC_RTN, 0x068]); // RTN delay + + let segment = self.biu_read_u8(ea_segment, ea_offset.wrapping_add(2)); + + self.cycle_i(0x06a); + self.biu_fetch_suspend(); + self.cycles_i(3, &[0x06b, 0x06c, MC_NONE]); + + // Push low byte of CS + self.push_u8((self.cs & 0x00FF) as u8, ReadWriteFlag::Normal); + + let next_i = self.ip(); + // We do not handle stepping over 0xFE call here as it is unlikely to lead to a valid location or return. + self.cs = 0xFF00 | segment as u16; + self.pc = 0xFF00 | offset as u16; + + self.cycles_i(3, &[0x06e, 0x06f, MC_JUMP]); // UNC NEARCALL + self.biu_queue_flush(); + self.cycles_i(3, &[0x077, 0x078, 0x079]); + + // Push low byte of next IP + self.push_u8((next_i & 0x00FF) as u8, ReadWriteFlag::RNI); + jump = true; + } + else if let OperandType::Register8(reg) = self.i.operand1_type { + + // Read one byte from DS:0004 (weird?) and don't do anything with it. + let _ = self.biu_read_u8(Segment::DS, 0x0004); + + // Push low byte of CS + self.push_u8((self.cs & 0x00FF) as u8, ReadWriteFlag::Normal); + // Push low byte of next IP + self.push_u8((self.ip() & 0x00FF) as u8, ReadWriteFlag::Normal); + + // temporary timings + self.biu_fetch_suspend(); + self.cycles(4); + self.biu_queue_flush(); + + // If this form uses a register operand, the full 16 bits are copied to PC. + self.pc = self.get_register16(NecVx0::reg8to16(reg)); + } + } + // Jump to memory r/m16 + Mnemonic::JMP => { + // Reads only 8 bit operand from modrm. + let ptr8 = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + + // Set only lower 8 bits of PC, upper bits FF + self.pc = 0xFF00 | ptr8 as u16; + + self.biu_fetch_suspend(); + self.cycles(4); + self.biu_queue_flush(); + jump = true; + } + // Jump Far + Mnemonic::JMPF => { + if let OperandType::AddressingMode(mode) = self.i.operand1_type { + let (ea_segment, ea_offset) = self.calc_effective_address(mode, None); + + // Read one byte of offset and one byte of segment + let offset = self.biu_read_u8(ea_segment, ea_offset); + let segment = self.biu_read_u8(ea_segment, ea_offset.wrapping_add(2)); + + self.biu_fetch_suspend(); + self.cycles(4); + self.biu_queue_flush(); + + self.cs = 0xFF00 | segment as u16; + self.pc = 0xFF00 | offset as u16; + jump = true; + } + else if let OperandType::Register8(reg) = self.i.operand1_type { + + // Read one byte from DS:0004 (weird?) and don't do anything with it. + let _ = self.biu_read_u8(Segment::DS, 0x0004); + + // temporary timings + self.biu_fetch_suspend(); + self.cycles(4); + self.biu_queue_flush(); + + // If this form uses a register operand, the full 16 bits are copied to PC. + self.pc = self.get_register16(NecVx0::reg8to16(reg)); + } + } + // Push Byte onto stack + Mnemonic::PUSH => { + // Read one byte from rm + let op_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + self.cycles_i(3, &[0x024, 0x025, 0x026]); + + // Write one byte to stack + self.push_u8(op_value, ReadWriteFlag::RNI); + } + _ => { + unhandled = true; + } + } + } + 0xFF => { + // Several opcode extensions here + match self.i.mnemonic { + Mnemonic::INC | Mnemonic::DEC => { + // INC/DEC r/m16 + let op_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let result = self.math_op16(self.i.mnemonic, op_value, 0); + + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycles_i(2,&[0x020, 0x021]); + } + self.write_operand16(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + }, + Mnemonic::CALL => { + + if let OperandType::AddressingMode(_) = self.i.operand1_type { + + let ptr16 = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + + self.biu_fetch_suspend(); + self.cycles_i(4, &[0x074, 0x075, MC_CORR, 0x076]); + + // Save next address if we step over this CALL. + self.step_over_target = Some(CpuAddress::Segmented(self.cs, self.ip())); + + let return_ip = self.ip(); + + // Add to call stack + self.push_call_stack( + CallStackEntry::Call { + ret_cs: self.cs, + ret_ip: return_ip, + call_ip: ptr16 + }, + self.cs, + return_ip + ); + + + self.pc = ptr16; + self.biu_queue_flush(); + self.cycles_i(3, &[0x077, 0x078, 0x079]); + + // Push return address (next instruction offset) onto stack + self.push_u16(return_ip, ReadWriteFlag::RNI); + + } + else if let OperandType::Register16(reg) = self.i.operand1_type { + // Register form is invalid (can't use arbitrary modrm register as a pointer) + // We model the odd behavior of this invalid form here. + self.biu_fetch_suspend(); + self.cycles_i(2, &[0x074, 0x075]); + self.corr();self.cycle_i(0x076); + + let next_i = self.pc; // PC already corrected above + self.pc = self.get_register16(reg); // Value of IP becomes value of register operand + self.biu_queue_flush(); + self.cycles_i(3, &[0x077, 0x078, 0x079]); + + // Push return address (next instruction offset) onto stack + self.push_u16(next_i, ReadWriteFlag::RNI); + } + + jump = true; + } + Mnemonic::CALLF => { + // CALL FAR r/mFarPtr + if let OperandType::AddressingMode(_mode) = self.i.operand1_type { + self.cycle_i(0x068); + let (segment, offset) = self.read_operand_farptr(self.i.operand1_type, self.i.segment_override, ReadWriteFlag::Normal).unwrap(); + let next_i = self.ip(); + + self.farcall(segment, offset, true); + + // Save next address if we step over this CALL. + self.step_over_target = Some(CpuAddress::Segmented(self.cs, next_i)); + + // Add to call stack + self.push_call_stack( + CallStackEntry::CallF { + ret_cs: self.cs, + ret_ip: next_i, + call_cs: segment, + call_ip: offset + }, + self.cs, + next_i + ); + } + else if let OperandType::Register16(_) = self.i.operand1_type { + // Register form is invalid (can't use arbitrary modrm register as a pointer) + // We model the odd behavior of this invalid form here. + + let seg = self.i.segment_override.unwrap_or(Segment::DS); + + // Read the segment from Seg:0004 + let offset = 0x0004; + let segment = self.biu_read_u16(seg, offset, ReadWriteFlag::Normal); + + self.cycle_i(0x06a); + self.biu_fetch_suspend(); + self.cycles_i(3, &[0x06b, 0x06c]); + self.corr(); + + // Push CS + self.push_register16(Register16::CS, ReadWriteFlag::Normal); + let next_i = self.pc; // PC already corrected above + self.cs = segment; + //self.ip = self.last_ea; // I am not sure where IP gets its value. + + self.cycles_i(3, &[0x06e, 0x06f, MC_JUMP]); + self.biu_queue_flush(); + self.cycles_i(3, &[0x077, 0x078, 0x079]); + + // Push next IP + self.push_u16(next_i, ReadWriteFlag::RNI); + } + jump = true; + } + // Jump to memory r/m16 + Mnemonic::JMP => { + let ptr16 = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + + self.biu_fetch_suspend(); + self.cycle_i(0x0d8); + self.pc = ptr16; + self.biu_queue_flush(); + jump = true; + } + // Jump Far + Mnemonic::JMPF => { + let offset; + + if let OperandType::AddressingMode(_mode) = self.i.operand1_type { + + self.cycle_i(0x0dc); + self.biu_fetch_suspend(); + self.cycle_i(0x0dd); + + let (segment, offset) = self.read_operand_farptr(self.i.operand1_type, self.i.segment_override, ReadWriteFlag::Normal).unwrap(); + + self.cs = segment; + self.pc = offset; + self.biu_queue_flush(); + } + else { + // Register form is invalid (can't use arbitrary modrm register as a pointer) + // We model the odd behavior of this invalid form here. + let seg = self.i.segment_override.unwrap_or(Segment::DS); + + self.cycle(); + self.biu_fetch_suspend(); + self.cycle(); + + // Read the segment from Seg:0004 + offset = 0x0004; + let segment = self.biu_read_u16(seg, offset, ReadWriteFlag::Normal); + + self.cs = segment; + self.biu_queue_flush(); + } + jump = true; + //log::trace!("JMPF: Destination [{:04X}:{:04X}]", segment, offset); + } + // Push Word onto stack + Mnemonic::PUSH => { + let mut op_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + self.cycles_i(3, &[0x024, 0x025, 0x026]); + + // If SP, push the new value of SP instead of the old value + if let OperandType::Register16(Register16::SP) = self.i.operand1_type { + op_value = op_value.wrapping_sub(2); + } + self.push_u16(op_value, ReadWriteFlag::RNI); + } + _=> { + unhandled = true; + } + } + } + } + + // Reset REP init flag. This flag is set after a rep-prefixed instruction is executed for the first time. It + // should be preserved between executions of a rep-prefixed instruction unless an interrupt occurs, in which + // case the rep-prefix instruction terminates normally after RPTI. This flag determines whether RPTS is + // run when executing the instruction. + if !self.in_rep { + self.rep_init = false; + } + else { + self.instruction_reentrant = true; + } + + if unhandled { + unreachable!("Invalid opcode!"); + //ExecutionResult::UnsupportedOpcode(self.i.opcode) + } + else if self.halted && !self.reported_halt && !self.get_flag(Flag::Interrupt) && !self.get_flag(Flag::Trap) { + // CPU was halted with interrupts disabled - will not continue + self.reported_halt = true; + ExecutionResult::Halt + } + else if jump { + ExecutionResult::OkayJump + } + else if self.in_rep { + if let RepType::MulDiv = self.rep_type { + // Rep prefix on MUL/DIV just sets flags, do not rep + self.in_rep = false; + Okay + } + else { + self.rep_init = true; + // Set step-over target so that we can skip long REP instructions. + // Normally the step behavior during REP is to perform a single iteration. + self.step_over_target = Some(CpuAddress::Segmented(self.cs, self.ip())); + ExecutionResult::OkayRep + } + } + else { + match exception { + CpuException::DivideError => ExecutionResult::ExceptionError(exception), + CpuException::NoException => Okay, + } + } + } +} diff --git a/core/src/cpu_vx0/fuzzer.rs b/core/src/cpu_vx0/fuzzer.rs new file mode 100644 index 00000000..5fc389d9 --- /dev/null +++ b/core/src/cpu_vx0/fuzzer.rs @@ -0,0 +1,370 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::fuzzer.rs + + Miscellaneous routines to generate random CPU state and instructions. + +*/ + +use rand::{Rng, SeedableRng}; + +use crate::{ + cpu_common::Segment, + cpu_vx0::{modrm::MODRM_REG_MASK, *}, +}; + +const RNG_SEED: u64 = 0x58158258u64; + +macro_rules! get_rand { + ($myself: expr) => { + $myself.rng.as_mut().unwrap().gen() + }; +} + +macro_rules! get_rand_range { + ($myself: expr, $begin: expr, $end: expr) => { + $myself.rng.as_mut().unwrap().gen_range($begin..$end) + }; +} + +impl NecVx0 { + #[allow(dead_code)] + pub fn randomize_seed(&mut self, mut seed: u64) { + if seed == 0 { + seed = RNG_SEED; + } + self.rng = Some(rand::rngs::StdRng::seed_from_u64(seed)); + } + + #[allow(dead_code)] + pub fn randomize_regs(&mut self) { + self.cs = get_rand!(self); + self.pc = get_rand!(self); + + self.set_reset_vector(CpuAddress::Segmented(self.cs, self.pc)); + self.reset(); + + for i in 0..REGISTER16_LUT.len() { + let n: u16 = get_rand!(self); + self.set_register16(REGISTER16_LUT[i], n); + } + + // Flush queue + self.queue.flush(); + + self.ds = get_rand!(self); + self.ss = get_rand!(self); + self.es = get_rand!(self); + + // Randomize flags + let mut flags: u16 = get_rand!(self); + // Clear trap & interrupt flags + flags &= !CPU_FLAG_TRAP; + flags &= !CPU_FLAG_INT_ENABLE; + + self.set_flags(flags); + + //self.set_flags(0); + } + + #[allow(dead_code)] + pub fn randomize_mem(&mut self) { + for i in 0..self.bus.size() { + let n: u8 = get_rand!(self); + self.bus.write_u8(i, n, 0).expect("Mem err"); + } + + // Write a basic IVT to handle DIV exceptions. + self.bus.write_u16(0x00000, 0x0400, 0).expect("Mem err writing IVT"); + self.bus.write_u16(0x00002, 0x0000, 0).expect("Mem err writing IVT"); + self.bus.write_u8(0x00400, 0xCF, 0).expect("Mem err writing IRET"); + } + + #[allow(dead_code)] + pub fn random_inst_from_opcodes(&mut self, opcode_list: &[u8]) { + let mut instr: VecDeque = VecDeque::new(); + + // Randomly pick one opcode from the provided list + let opcode_i = get_rand_range!(self, 0, opcode_list.len()); + let opcode = opcode_list[opcode_i]; + + instr.push_back(opcode); + + let mut enable_segment_prefix = true; + + // Add rep prefixes to string ops with 50% probability + let do_rep_prefix: u8 = get_rand!(self); + match opcode { + 0xA4..=0xA7 | 0xAA..=0xAF => { + // String ops + match do_rep_prefix { + 0..=64 => { + instr.push_front(0xF2); // REPNZ + } + 65..=128 => { + instr.push_front(0xF3); // REPZ + } + _ => {} + } + + // Mask CX to 8 bits. + //self.cx = self.cx & 0x00FF; + } + 0x9D => { + // POPF. + // We need to modify the word at SS:SP to clear the trap flag bit. + + let flat_addr = self.calc_linear_address_seg(Segment::SS, self.sp); + + let (mut flag_word, _) = self + .bus_mut() + .read_u16(flat_addr as usize, 0) + .expect("Couldn't read stack!"); + + // Clear trap flag + flag_word = flag_word & !CPU_FLAG_TRAP; + + self.bus_mut() + .write_u16(flat_addr as usize, flag_word, 0) + .expect("Couldn't write stack!"); + } + 0xCF => { + // IRET. + // We need to modify the word at SS:SP + 4 to clear the trap flag bit. + + let flat_addr = self.calc_linear_address_seg(Segment::SS, self.sp.wrapping_add(4)); + + let (mut flag_word, _) = self + .bus_mut() + .read_u16(flat_addr as usize, 0) + .expect("Couldn't read stack!"); + + // Clear trap flag + flag_word = flag_word & !CPU_FLAG_TRAP; + + self.bus_mut() + .write_u16(flat_addr as usize, flag_word, 0) + .expect("Couldn't write stack!"); + } + 0xD2 | 0xD3 => { + // Shifts and rotates by cl. + // Mask CL to 6 bits to shorten tests. + // This will still catch emulators that are masking CL to 5 bits. + + self.c.set_l(self.c.l() & 0x3F); + } + 0xC0..=0xC3 | 0xC8..=0xCF => { + // RETN, RETF, INT[X], IRET + enable_segment_prefix = false; + } + 0xF5 | 0xF8..=0xFD => { + // Clear/set flags + enable_segment_prefix = false; + } + _ => {} + } + + let mut modrm_valid = false; + let mut modrm_byte: u8 = get_rand!(self); + + while !modrm_valid { + modrm_byte = get_rand!(self); + + // Filter out invalid forms of some instructions that cannot + // reasonably be validated. + match opcode { + // LEA + 0x8D => { + if modrm_byte & 0xC0 == 0xC0 { + // Reg form, invalid. + continue; + } + } + // LES | LDS + 0xC4 | 0xC5 => { + if modrm_byte & 0xC0 == 0xC0 { + // Reg form, invalid. + continue; + } + } + // POP + 0x8F => { + if (modrm_byte >> 3) & 0x07 != 0 { + // reg != 0, invalid. + continue; + } + if (modrm_byte & 0xC0) == 0xC0 { + // register form invalid + continue; + } + //log::debug!("Picked valid modrm for 0x8F: {:02X}", modrm_byte); + } + _ => {} + } + + modrm_valid = true; + } + + // Add 'modrm' byte (even if not used) + //let modrm_byte: u8 = get_rand!(self); + + instr.push_back(modrm_byte); + + // Add a segment override prefix with 50% probability + let do_segment_prefix: u8 = get_rand!(self); + + if enable_segment_prefix && do_segment_prefix > 127 { + // use last 4 bits to determine prefix + match do_segment_prefix & 0x03 { + 0b00 => instr.push_front(0x26), // ES override + 0b01 => instr.push_front(0x2E), // CS override + 0b10 => instr.push_front(0x36), // SS override + 0b11 => instr.push_front(0x3E), // DS override + _ => {} + } + } + + // Add five random instruction bytes (+modrm makes 6) + for _ in 0..5 { + let instr_byte: u8 = get_rand!(self); + + instr.push_back(instr_byte); + } + + // Copy instruction to memory at CS:IP + let addr = NecVx0::calc_linear_address(self.cs, self.pc); + log::debug!("Using instruction vector: {:X?}", instr.make_contiguous()); + self.bus + .copy_from(instr.make_contiguous(), (addr & 0xFFFFF) as usize, 0, false) + .unwrap(); + } + + #[allow(dead_code)] + pub fn random_grp_instruction(&mut self, opcode: u8, extension_list: &[u8]) { + let mut instr: VecDeque = VecDeque::new(); + + // Randomly pick one extension from the provided list + let extension_i = get_rand_range!(self, 0, extension_list.len()); + let extension = extension_list[extension_i]; + + instr.push_back(opcode); + + let do_rep_prefix: u8 = get_rand!(self); + + match (opcode, extension) { + (0xF6 | 0xF7, 0x07) => { + // IDIV + // REP prefixes on IDIV invert quotient (undocumented) + match do_rep_prefix { + 0..=0x5 => { + // Inject REP prefix at 5% probability + instr.push_front(0xF2); // REPNZ + } + 0x06..=0x10 => { + // Inject REP prefix at 5% probability + instr.push_front(0xF3); // REPZ + } + _ => {} + } + } + _ => {} + } + + // Add a segment override prefix with 50% probability + let do_segment_prefix: u8 = get_rand!(self); + + if do_segment_prefix > 127 { + // use last 4 bits to determine prefix + match do_segment_prefix & 0x03 { + 0b00 => instr.push_front(0x26), // ES override + 0b01 => instr.push_front(0x2E), // CS override + 0b10 => instr.push_front(0x36), // SS override + 0b11 => instr.push_front(0x3E), // DS override + _ => {} + } + } + + let mut modrm_valid = false; + // Add a modrm + let mut modrm_byte: u8 = get_rand!(self); + + while !modrm_valid { + modrm_byte = get_rand!(self); + + // Inject the operand extension. First, clear the REG bits + modrm_byte &= !MODRM_REG_MASK; + + // Now set the reg bits to extension # + modrm_byte |= (extension << 3) & MODRM_REG_MASK; + + // Filter out invalid forms of some instructions that cannot + // reasonably be validated. + match opcode { + // FF group opcode + 0xFF => { + match modrm_byte & 0b00_111_000 { + 0b00_011_000 => { + // FF.3 CALLF + if modrm_byte & 0xC0 == 0xC0 { + // Reg form, invalid. + continue; + } + } + 0b00_101_000 => { + // FF.5 JMPF + if modrm_byte & 0xC0 == 0xC0 { + // Reg form, invalid. + continue; + } + } + _ => {} + } + } + _ => {} + } + + modrm_valid = true; + } + + // Finally push the modrm + instr.push_back(modrm_byte); + + // Add five random instruction bytes (6 - modrm) + for _ in 0..6 { + let instr_byte: u8 = get_rand!(self); + + instr.push_back(instr_byte); + } + + // Copy instruction to memory at CS:IP + let addr = NecVx0::calc_linear_address(self.cs, self.pc); + log::debug!("Using instruction vector: {:X?}", instr.make_contiguous()); + self.bus + .copy_from(instr.make_contiguous(), addr as usize, 0, false) + .unwrap(); + } +} diff --git a/core/src/cpu_vx0/gdr.rs b/core/src/cpu_vx0/gdr.rs new file mode 100644 index 00000000..88a483c5 --- /dev/null +++ b/core/src/cpu_vx0/gdr.rs @@ -0,0 +1,89 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::gdr.rs + + Provides routines for querying an entry of the Group Decode ROM. + + Microcode disassembly by reenigne: + https://www.reenigne.org/blog/8086-microcode-disassembled/ + +*/ +#![allow(dead_code)] + +/// Technically a PLA, the Group Decode ROM emits 15 signals given an 8-bit opcode for output. +/// These signals are encoded as a bitfield. +pub const GDR_IO: u16 = 0b0000_0000_0000_0001; // Instruction is an I/O instruction +pub const GDR_NO_LOAD_EA: u16 = 0b0000_0000_0000_0010; // Instruction does not load its EA (write-only) +pub const GDR_GRP_345: u16 = 0b0000_0000_0000_0100; // Instruction is a Group 3, 4, or 5 instruction +pub const GDR_PREFIX: u16 = 0b0000_0000_0000_1000; // Instruction is a prefix byte +pub const GDR_NO_MODRM: u16 = 0b0000_0000_0001_0000; // Instruction does not have a modrm byte +pub const GDR_SPECIAL_ALU: u16 = 0b0000_0000_0010_0000; +pub const GDR_CLEARS_COND: u16 = 0b0000_0000_0100_0000; +pub const GDR_USES_AREG: u16 = 0b0000_0000_1000_0000; // Instruction uses the AL or AX register specifically +pub const GDR_USES_SREG: u16 = 0b0000_0001_0000_0000; // Instruction uses a segment register +pub const GDR_D_VALID: u16 = 0b0000_0010_0000_0000; // 'D' bit is valid for instruction +pub const GRD_NO_MC: u16 = 0b0000_0100_0000_0000; // Instruction has no microcode +pub const GDR_W_VALID: u16 = 0b0000_1000_0000_0000; // 'W' bit is valid for instruction +pub const GDR_FORCE_BYTE: u16 = 0b0001_0000_0000_0000; // Instruction forces a byte operation +pub const GDR_L8: u16 = 0b0010_0000_0000_0000; // Instruction sets L8 flag +pub const GDR_UPDATE_CARRY: u16 = 0b0100_0000_0000_0000; // Instruction updates Carry flag + +pub struct GdrEntry(pub u16); + +impl GdrEntry { + pub fn new(data: u16) -> Self { + Self(data) + } + #[inline] + pub fn get(&self) -> u16 { + self.0 + } + #[inline(always)] + pub fn has_modrm(&self) -> bool { + self.0 & GDR_NO_MODRM == 0 + } + #[inline(always)] + pub fn loads_ea(&self) -> bool { + self.0 & GDR_NO_LOAD_EA == 0 + } + #[inline(always)] + pub fn w_valid(&self) -> bool { + self.0 & GDR_W_VALID != 0 + } + #[inline(always)] + pub fn d_valid(&self) -> bool { + self.0 & GDR_D_VALID != 0 + } + #[inline(always)] + pub fn force_byte(&self) -> bool { + self.0 & GDR_FORCE_BYTE != 0 + } + #[inline(always)] + pub fn set_l8(&self) -> bool { + self.0 & GDR_L8 != 0 + } +} diff --git a/core/src/cpu_vx0/instruction.rs b/core/src/cpu_vx0/instruction.rs new file mode 100644 index 00000000..c2af9590 --- /dev/null +++ b/core/src/cpu_vx0/instruction.rs @@ -0,0 +1,31 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::instruction.rs + + Definition of the Instruction struct and related methods. + +*/ diff --git a/core/src/cpu_vx0/interrupt.rs b/core/src/cpu_vx0/interrupt.rs new file mode 100644 index 00000000..4a754b4e --- /dev/null +++ b/core/src/cpu_vx0/interrupt.rs @@ -0,0 +1,327 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::interrupt.rs + + Routines to handle interrupts. + +*/ + +use crate::{ + cpu_common::{Segment, ServiceEvent}, + cpu_vx0::*, +}; + +impl NecVx0 { + /// Execute the IRET microcode routine. + pub fn iret_routine(&mut self) { + self.cycle_i(0x0c8); + self.farret(true); + self.pop_flags(); + self.cycle_i(0x0ca); + } + + /// Perform a software interrupt + pub fn sw_interrupt(&mut self, interrupt: u8) { + // Interrupt FC, emulator internal services. + if self.enable_service_interrupt && interrupt == 0xFC { + match self.a.h() { + 0x01 => { + // TODO: Make triggering pit logging a separate service number. Just re-using this one + // out of laziness. + self.service_events.push_back(ServiceEvent::TriggerPITLogging); + + log::debug!( + "Received emulator trap interrupt: CS: {:04X} IP: {:04X}", + self.b.x(), + self.c.x() + ); + self.biu_fetch_suspend(); + self.cycles(4); + + self.cs = self.b.x(); + self.pc = self.c.x(); + + // Set execution segments + self.ds = self.cs; + self.es = self.cs; + self.ss = self.cs; + // Create stack + self.sp = 0xFFFE; + + self.biu_queue_flush(); + self.cycles(4); + self.set_breakpoint_flag(); + } + _ => {} + } + return; + } + + self.cycles_i(3, &[0x19d, 0x19e, 0x19f]); + + // Read the IVT + let vec_addr = (interrupt as usize * INTERRUPT_VEC_LEN) as u16; + + let new_ip = self.biu_read_u16(Segment::None, vec_addr, ReadWriteFlag::Normal); + self.cycle_i(0x1a1); + let new_cs = self.biu_read_u16(Segment::None, vec_addr.wrapping_add(2), ReadWriteFlag::Normal); + + // Add interrupt to call stack + self.push_call_stack( + CallStackEntry::Interrupt { + ret_cs: self.cs, + ret_ip: self.ip(), + call_cs: new_cs, + call_ip: new_ip, + itype: InterruptType::Software, + number: interrupt, + ah: self.a.h(), + }, + self.cs, + self.ip(), + ); + + self.biu_fetch_suspend(); // 1a3 SUSP + self.cycles_i(2, &[0x1a3, 0x1a4]); + self.push_flags(ReadWriteFlag::Normal); + self.clear_flag(Flag::Interrupt); + self.clear_flag(Flag::Trap); + self.cycle_i(0x1a6); + self.farcall2(new_cs, new_ip); + self.int_count += 1; + } + + /* + /// Handle a CPU exception + pub fn handle_exception(&mut self, exception: u8) { + self.push_flags(ReadWriteFlag::Normal); + + // Push return address of next instruction onto stack + self.push_register16(Register16::CS, ReadWriteFlag::Normal); + + // Don't push address of next instruction + self.push_u16(self.ip, ReadWriteFlag::Normal); + + if exception == 0x0 { + log::trace!( + "CPU Exception: {:02X} Saving return: {:04X}:{:04X}", + exception, + self.cs, + self.ip + ); + } + // Read the IVT + let ivt_addr = Cpu::calc_linear_address(0x0000, (exception as usize * INTERRUPT_VEC_LEN) as u16); + let (new_ip, _cost) = self.bus.read_u16(ivt_addr as usize, 0).unwrap(); + let (new_cs, _cost) = self.bus.read_u16((ivt_addr + 2) as usize, 0).unwrap(); + + // Add interrupt to call stack + self.push_call_stack( + CallStackEntry::Interrupt { + ret_cs: self.cs, + ret_ip: self.ip, + call_cs: new_cs, + call_ip: new_ip, + itype: InterruptType::Exception, + number: exception, + ah: self.ah, + }, + self.cs, + self.ip, + ); + + self.ip = new_ip; + self.cs = new_cs; + + // Flush queue + self.biu_queue_flush(); + self.biu_update_pc(); + } + */ + #[allow(dead_code)] + pub fn log_interrupt(&self, interrupt: u8) { + match interrupt { + 0x10 => { + // Video Services + match self.a.h() { + 0x00 => { + log::trace!( + "CPU: Video Interrupt: {:02X} (AH:{:02X} Set video mode) Video Mode: {:02X}", + interrupt, + self.a.h(), + self.a.l() + ); + } + 0x01 => { + log::trace!( + "CPU: Video Interrupt: {:02X} (AH:{:02X} Set text-mode cursor shape: CH:{:02X}, CL:{:02X})", + interrupt, + self.a.h(), + self.c.h(), + self.c.l() + ); + } + 0x02 => { + log::trace!("CPU: Video Interrupt: {:02X} (AH:{:02X} Set cursor position): Page:{:02X} Row:{:02X} Col:{:02X}", + interrupt, self.a.h(), self.b.h(), self.d.h(), self.d.l()); + } + 0x09 => { + log::trace!("CPU: Video Interrupt: {:02X} (AH:{:02X} Write character and attribute): Char:'{}' Page:{:02X} Color:{:02x} Ct:{:02}", + interrupt, self.a.h(), self.a.l() as char, self.b.h(), self.b.l(), self.c.x()); + } + 0x10 => { + log::trace!( + "CPU: Video Interrupt: {:02X} (AH:{:02X} Write character): Char:'{}' Page:{:02X} Ct:{:02}", + interrupt, + self.a.h(), + self.a.l() as char, + self.b.h(), + self.c.x() + ); + } + _ => {} + } + } + _ => {} + }; + } + + /// Execute the INTR microcode routine. + /// skip_first is used to skip the first microcode instruction, such as when entering from + /// INT1 or INT2. + pub fn intr_routine(&mut self, vector: u8, itype: InterruptType, skip_first: bool) { + // Check for interrupt breakpoint. + if self.int_flags[vector as usize] & INTERRUPT_BREAKPOINT != 0 { + self.set_breakpoint_flag(); + } + + if !skip_first { + self.cycle_i(0x019d); + } + self.cycles_i(2, &[0x19e, 0x19f]); + + // Read the IVT + let vec_addr = (vector as usize * INTERRUPT_VEC_LEN) as u16; + + let new_ip = self.biu_read_u16(Segment::None, vec_addr, ReadWriteFlag::Normal); + self.cycle_i(0x1a1); + let new_cs = self.biu_read_u16(Segment::None, vec_addr.wrapping_add(2), ReadWriteFlag::Normal); + + // Add interrupt to call stack + self.push_call_stack( + CallStackEntry::Interrupt { + ret_cs: self.cs, + ret_ip: self.ip(), + call_cs: new_cs, + call_ip: new_ip, + itype, + number: vector, + ah: self.a.h(), + }, + self.cs, + self.ip(), + ); + + self.biu_fetch_suspend(); // 1a3 SUSP + self.cycles_i(2, &[0x1a3, 0x1a4]); + self.push_flags(ReadWriteFlag::Normal); + self.clear_flag(Flag::Interrupt); + self.clear_flag(Flag::Trap); + self.cycle_i(0x1a6); + + self.farcall2(new_cs, new_ip); + } + + /// Perform a hardware interrupt + pub fn hw_interrupt(&mut self, vector: u8) { + self.in_int = true; + // Begin IRQ routine + self.biu_inta(vector); + self.biu_fetch_suspend(); + self.cycles_i(2, &[0x19b, 0x19c]); + + // Begin INTR routine + self.intr_routine(vector, InterruptType::Hardware, false); + self.int_count += 1; + self.in_int = false; + } + + /// Perform INT0 (Divide By 0) + pub fn int0(&mut self) { + self.cycles_i(2, &[0x1a7, MC_JUMP]); + self.intr_routine(0, InterruptType::Exception, true); + self.int_count += 1; + } + + /// Perform INT1 (Trap) + pub fn int1(&mut self) { + self.cycles_i(2, &[0x198, MC_JUMP]); + self.intr_routine(1, InterruptType::Exception, true); + self.int_count += 1; + } + + /// Perform INT2 (NMI) + pub fn int2(&mut self) { + self.cycles_i(2, &[0x199, MC_JUMP]); + self.intr_routine(2, InterruptType::Exception, true); + self.int_count += 1; + } + + /// Perform INT3 + pub fn int3(&mut self) { + self.cycles_i(4, &[0x1b0, MC_JUMP, 0x1b2, MC_JUMP]); + self.intr_routine(3, InterruptType::Software, false); + self.int_count += 1; + } + + /// Perform INTO + pub fn int_o(&mut self) { + self.cycles_i(4, &[0x1ac, 0x1ad]); + + if self.get_flag(Flag::Overflow) { + self.cycles_i(2, &[0x1af, MC_JUMP]); + self.intr_routine(4, InterruptType::Hardware, false); + self.int_count += 1; + } + } + + /// Return true if an interrupt can occur under current execution state + #[inline] + pub fn interrupts_enabled(&self) -> bool { + self.get_flag(Flag::Interrupt) && !self.interrupt_inhibit + } + + /// Returns true if a trap can occur under current execution state. + #[inline] + pub fn trap_enabled(&self) -> bool { + // Trap if trap flag is set, OR trap flag has been cleared but disable delay in effect (to trap POPF that clears trap) + // but only if trap is not suppressed and enable delay is 0. + (self.get_flag(Flag::Trap) || self.trap_disable_delay != 0) + && !self.trap_suppressed + && self.trap_enable_delay == 0 + } +} diff --git a/core/src/cpu_vx0/jump.rs b/core/src/cpu_vx0/jump.rs new file mode 100644 index 00000000..8d4fc91f --- /dev/null +++ b/core/src/cpu_vx0/jump.rs @@ -0,0 +1,134 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::jump.rs + + Implements microcode routines for jumps and calls. +*/ + +use crate::{ + cpu_vx0::{biu::*, *}, + util, +}; + +impl NecVx0 { + /* + /// Execute the RELJMP microcode routine, optionally including the jump into the procedure. + #[inline] + pub fn reljmp(&mut self, new_pc: u16, jump: bool) { + if jump { + self.cycle_i(MC_JUMP); + } + //self.biu_fetch_suspend_i(0x0d2); + self.biu_fetch_suspend(); + self.cycles_i(4, &[0x0d2, 0x0d3, MC_CORR, 0x0d4]); + self.pc = new_pc; + self.biu_queue_flush(); // 0d5 + self.cycle_i(0x0d5); + }*/ + + /// Execute the RELJMP microcode routine, optionally including the jump into the procedure. + #[inline] + pub fn reljmp2(&mut self, rel: i16, jump: bool) { + //TODO: avoid branching. separate functions? make caller handle? + if jump { + self.cycle_i(MC_JUMP); + } + //self.biu_fetch_suspend_i(0x0d2); + self.biu_fetch_suspend(); + self.cycles_i(2, &[0x0d2, 0x0d3]); + self.corr(); + self.pc = util::relative_offset_u16(self.pc, rel); + self.cycle_i(0x0d4); + + self.biu_queue_flush(); // 0d5 + self.cycle_i(0x0d5); + } + + /// Execute the FARCALL microcode routine. + #[inline] + pub fn farcall(&mut self, new_cs: u16, new_ip: u16, jump: bool) { + if jump { + self.cycle_i(MC_JUMP); + } + self.biu_fetch_suspend(); // 0x06B + self.cycles_i(2, &[0x06b, 0x06c]); + self.corr(); + // Push return segment to stack + self.push_u16(self.cs, ReadWriteFlag::Normal); + self.cs = new_cs; + self.cycles_i(2, &[0x06e, 0x06f]); + self.nearcall(new_ip); + } + + /// Execute the FARCALL2 microcode routine. Called by interrupt procedures. + #[inline] + pub fn farcall2(&mut self, new_cs: u16, new_ip: u16) { + self.cycles_i(2, &[MC_JUMP, 0x06c]); + self.corr(); + // Push return segment to stack + self.push_u16(self.cs, ReadWriteFlag::Normal); + self.cs = new_cs; + self.cycles_i(2, &[0x06e, 0x06f]); + self.nearcall(new_ip); + } + + /// Execute the NEARCALL microcode routine. + #[inline] + pub fn nearcall(&mut self, new_ip: u16) { + let ret_ip = self.pc; // NEARCALL assumes that CORR was called prior + self.cycle_i(MC_JUMP); + self.pc = new_ip; + self.biu_queue_flush(); + self.cycles_i(3, &[0x077, 0x078, 0x079]); + self.push_u16(ret_ip, ReadWriteFlag::RNI); + } + + /// Execute the FARRET microcode routine, including the jump into the procedure. + pub fn farret(&mut self, far: bool) { + self.cycle_i(MC_JUMP); + //self.pop_register16(Register16::IP, ReadWriteFlag::RNI); + self.pc = self.pop_u16(); + self.biu_fetch_suspend(); + //self.cycle_i(MC_NONE); + self.cycles_i(2, &[0x0c3, 0x0c4]); + + let far2 = self.i.opcode & 0x08 != 0; + assert_eq!(far, far2); + + if far { + self.cycle_i(MC_JUMP); + self.pop_register16(Register16::CS, ReadWriteFlag::Normal); + + self.biu_queue_flush(); + self.cycles_i(2, &[0x0c7, MC_RTN]); + } + else { + self.biu_queue_flush(); + self.cycles_i(2, &[0x0c5, MC_RTN]); + } + } +} diff --git a/core/src/cpu_vx0/logging.rs b/core/src/cpu_vx0/logging.rs new file mode 100644 index 00000000..fa466c42 --- /dev/null +++ b/core/src/cpu_vx0/logging.rs @@ -0,0 +1,694 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::logging.rs + + Implements cycle-state logging facilities. + +*/ + +use crate::{ + cpu_common::{QueueOp, Segment, TraceMode}, + cpu_vx0::{ + BusStatus, + Cpu, + DmaState, + NecVx0, + TCycle, + TaCycle, + CPU_FLAG_AUX_CARRY, + CPU_FLAG_CARRY, + CPU_FLAG_DIRECTION, + CPU_FLAG_INT_ENABLE, + CPU_FLAG_OVERFLOW, + CPU_FLAG_PARITY, + CPU_FLAG_SIGN, + CPU_FLAG_TRAP, + CPU_FLAG_ZERO, + }, + syntax_token::SyntaxToken, +}; + +pub enum BusSlotStatus { + SlotA(BusStatus, TCycle), + SlotB(BusStatus, TaCycle), +} + +impl NecVx0 { + #[inline] + pub fn do_cycle_trace(&mut self) { + match self.trace_mode { + TraceMode::CycleText => { + // Get value of timer channel #1 for DMA printout + let mut dma_count = 0; + + if let Some(pit) = self.bus.pit_mut().as_mut() { + (_, dma_count, _) = pit.get_channel_count(1); + } + + let state_str = self.cycle_state_string(dma_count, false); + self.trace_print(&state_str); + self.trace_str_vec.push(state_str); + + self.trace_comment.clear(); + } + TraceMode::CycleCsv => { + // Get value of timer channel #1 for DMA printout + let mut dma_count = 0; + + if let Some(pit) = self.bus.pit_mut().as_mut() { + (_, dma_count, _) = pit.get_channel_count(1); + } + + let token_vec = self.cycle_state_tokens(dma_count, false); + //self.trace_print(&state_str); + self.trace_token_vec.push(token_vec); + + self.trace_comment.clear(); + } + TraceMode::CycleSigrok => { + self.trace_csv_line(); + } + _ => {} + } + } + + pub fn instruction_state_string(&self, last_cs: u16, last_ip: u16) -> String { + let mut instr_str = String::new(); + + instr_str.push_str(&format!("{:04x}:{:04x} {}\n", last_cs, last_ip, self.i)); + instr_str.push_str(&format!( + "AX: {:04x} BX: {:04x} CX: {:04x} DX: {:04x}\n", + self.a.x(), + self.b.x(), + self.c.x(), + self.d.x() + )); + instr_str.push_str(&format!( + "SP: {:04x} BP: {:04x} SI: {:04x} DI: {:04x}\n", + self.sp, self.bp, self.si, self.di + )); + instr_str.push_str(&format!( + "CS: {:04x} DS: {:04x} ES: {:04x} SS: {:04x}\n", + self.cs, self.ds, self.es, self.ss + )); + instr_str.push_str(&format!("IP: {:04x} FLAGS: {:04x}", self.ip(), self.flags)); + + instr_str + } + + pub fn emit_header(&mut self) { + match self.trace_mode { + TraceMode::CycleCsv => self.trace_print("Time(s),addr,clk,ready,qs,s,clk0,intr,dr0,holda,vs,hs,den,brd"), + _ => {} + } + } + + pub fn trace_csv_line(&mut self) { + let q = self.last_queue_op as u8; + let s = self.bus_status as u8; + + let mut vs = 0; + let mut hs = 0; + let mut den = 0; + let mut brd = 0; + if let Some(video) = self.bus().primary_video() { + let (vs_b, hs_b, den_b, brd_b) = video.get_sync(); + vs = vs_b as u8; + hs = hs_b as u8; + den = den_b as u8; + brd = brd_b as u8; + } + + // Segment status bits are valid after ALE. + if !self.i8288.ale { + let seg_n = match self.bus_segment { + Segment::ES => 0, + Segment::SS => 1, + Segment::CS | Segment::None => 2, + Segment::DS => 3, + }; + self.address_bus = (self.address_bus & 0b1100_1111_1111_1111_1111) | (seg_n << 16); + } + + // "Time(s),addr,clk,ready,qs,s,clk0,intr,dr0,vs,hs" + // sigrok import string: + // t,x20,l,l,x2,x3,l,l,l,l,l,l + self.trace_emit(&format!( + "{},{:05X},1,{},{},{},{},{},{},{},{},{},{},{}", + self.t_stamp, + self.address_bus, + self.ready as u8, + q, + s, + self.clk0 as u8, + self.intr as u8, + self.dma_req as u8, + self.dma_holda as u8, + vs, + hs, + den, + brd + )); + + self.trace_emit(&format!( + "{},{:05X},0,{},{},{},{},{},{},{},{},{},{},{}", + self.t_stamp + self.t_step_h, + self.address_bus, + self.ready as u8, + q, + s, + self.clk0 as u8, + self.intr as u8, + self.dma_req as u8, + self.dma_holda as u8, + vs, + hs, + den, + brd + )); + } + + pub fn cycle_state_string(&self, dma_count: u16, short: bool) -> String { + let ale_str = match self.i8288.ale { + true => "A:", + false => " ", + }; + + let mut seg_str = " "; + if self.t_cycle != TCycle::T1 { + // Segment status only valid in T2+ + seg_str = match self.bus_segment { + Segment::None => " ", + Segment::SS => "SS", + Segment::ES => "ES", + Segment::CS => "CS", + Segment::DS => "DS", + }; + } + + let q_op_chr = match self.last_queue_op { + QueueOp::Idle => ' ', + QueueOp::First => 'F', + QueueOp::Flush => 'E', + QueueOp::Subsequent => 'S', + }; + + let q_preload_char = match self.queue.has_preload() { + true => '*', + false => ' ', + }; + + /* + let mut f_op_chr = match self.fetch_state { + FetchState::Scheduled(_) => 'S', + FetchState::Aborted(_) => 'A', + //FetchState::Suspended => '!', + _ => ' ' + }; + + if self.fetch_suspended { + f_op_chr = '!' + } + */ + + // All read/write signals are active/low + let rs_chr = match self.i8288.mrdc { + true => 'R', + false => '.', + }; + let aws_chr = match self.i8288.amwc { + true => 'A', + false => '.', + }; + let ws_chr = match self.i8288.mwtc { + true => 'W', + false => '.', + }; + let ior_chr = match self.i8288.iorc { + true => 'R', + false => '.', + }; + let aiow_chr = match self.i8288.aiowc { + true => 'A', + false => '.', + }; + let iow_chr = match self.i8288.iowc { + true => 'W', + false => '.', + }; + + let is_reading = self.i8288.mrdc | self.i8288.iorc; + let is_writing = self.i8288.mwtc | self.i8288.iowc; + + let mut xfer_str = " ".to_string(); + if is_reading { + xfer_str = format!("<-r {:02X}", self.data_bus); + } + else if is_writing { + xfer_str = format!("w-> {:02X}", self.data_bus); + } + + // Handle queue activity + + let mut q_read_str = " ".to_string(); + + let mut instr_str = String::new(); + + if self.last_queue_op == QueueOp::First || self.last_queue_op == QueueOp::Subsequent { + // Queue byte was read. + q_read_str = format!("<-q {:02X}", self.last_queue_byte); + } + + if self.last_queue_op == QueueOp::First { + // First byte of opcode read from queue. Decode the full instruction + instr_str = format!( + "[{:04X}:{:04X}] {} ({}) ", + self.cs, self.instruction_ip, self.i, self.i.size + ); + } + + let _dma_dreq_chr = match self.dma_aen { + true => 'R', + false => '.', + }; + + let tx_cycle = match self.is_last_wait() { + true => 'x', + false => '.', + }; + + let ready_chr = if self.wait_states > 0 { '.' } else { 'R' }; + + let dma_count_str = &format!("{:02} {:02}", dma_count, self.dram_refresh_cycle_num); + + let dma_str = match self.dma_state { + DmaState::Idle => dma_count_str, + DmaState::Dreq => "DREQ", + DmaState::Hrq => "HRQ ", + DmaState::HoldA => "HLDA", + DmaState::Operating(n) => match n { + 0 => "S1", + 1 => "S2", + 2 => "S3", + 3 => "S4", + _ => dma_count_str, + }, //DmaState::DmaWait(..) => "DMAW" + DmaState::End => "END", + }; + + let mut cycle_str; + + let (slot0bus, slot0t, slot1bus, slot1t) = self.get_pl_slot_strings(); + + if short { + cycle_str = format!( + "{:04} {:02}[{:05X}] {:02} {}{} M:{}{}{} I:{}{}{} |{:5}| {:04} {:02} | {:06} | {:<14}| {:1}{:1}{:1}[{:08}] {} | {}", + self.instr_cycle, + ale_str, + self.address_latch, + seg_str, + ready_chr, + self.wait_states, + rs_chr, aws_chr, ws_chr, ior_chr, aiow_chr, iow_chr, + dma_str, + self.bus_status, + self.t_cycle, + xfer_str, + format!("{:?}", self.bus_pending), + q_op_chr, + self.last_queue_len, + q_preload_char, + self.queue.to_string(), + q_read_str, + instr_str + ); + } + else { + cycle_str = format!( + "{:08}:{:04} {:02}[{:05X}] {:02} {}{}{} M:{}{}{} I:{}{}{} |{:5}| {:04} {:02} | {:04} {:02} {:04} {:02} | {:06} | {:<8}| {:<10} | {:1}{:1}{:1}[{:08}] {} | {}", + self.cycle_num, + self.instr_cycle, + ale_str, + self.address_latch, + seg_str, + ready_chr, + self.wait_states, + tx_cycle, + rs_chr, aws_chr, ws_chr, ior_chr, aiow_chr, iow_chr, + dma_str, + self.bus_status, + self.t_cycle, + slot0bus, + slot0t, + slot1bus, + slot1t, + xfer_str, + format!("{:?}", self.bus_pending), + format!("{:?}", self.fetch_state), + q_op_chr, + self.last_queue_len, + q_preload_char, + self.queue.to_string(), + q_read_str, + instr_str + ); + } + + for c in &self.trace_comment { + cycle_str.push_str(&format!("; {}", c)); + } + + cycle_str + } + + pub fn cycle_state_tokens(&self, dma_count: u16, _short: bool) -> Vec { + let ale_str = match self.i8288.ale { + true => "A", + false => " ", + } + .to_string(); + let ale_token = SyntaxToken::Text(ale_str); + + let mut seg_str = " "; + if self.t_cycle != TCycle::T1 { + // Segment status only valid in T2+ + seg_str = match self.bus_segment { + Segment::None => " ", + Segment::SS => "SS", + Segment::ES => "ES", + Segment::CS => "CS", + Segment::DS => "DS", + }; + } + let seg_token = SyntaxToken::Text(seg_str.to_string()); + + let q_op_chr = match self.last_queue_op { + QueueOp::Idle => ' ', + QueueOp::First => 'F', + QueueOp::Flush => 'E', + QueueOp::Subsequent => 'S', + }; + let q_op_token = SyntaxToken::Text(q_op_chr.to_string()); + + let _q_preload_char = match self.queue.has_preload() { + true => '*', + false => ' ', + }; + + /* + let mut f_op_chr = match self.fetch_state { + FetchState::Scheduled(_) => 'S', + FetchState::Aborted(_) => 'A', + //FetchState::Suspended => '!', + _ => ' ' + }; + + if self.fetch_suspended { + f_op_chr = '!' + } + */ + + // All read/write signals are active/low + let rs_chr = match self.i8288.mrdc { + true => 'R', + false => '.', + }; + let aws_chr = match self.i8288.amwc { + true => 'A', + false => '.', + }; + let ws_chr = match self.i8288.mwtc { + true => 'W', + false => '.', + }; + let ior_chr = match self.i8288.iorc { + true => 'R', + false => '.', + }; + let aiow_chr = match self.i8288.aiowc { + true => 'A', + false => '.', + }; + let iow_chr = match self.i8288.iowc { + true => 'W', + false => '.', + }; + + let bus_str = match self.bus_status_latch { + BusStatus::InterruptAck => "IRQA", + BusStatus::IoRead => "IOR ", + BusStatus::IoWrite => "IOW ", + BusStatus::Halt => "HALT", + BusStatus::CodeFetch => "CODE", + BusStatus::MemRead => "MEMR", + BusStatus::MemWrite => "MEMW", + BusStatus::Passive => "PASV", + }; + let bus_str_token = SyntaxToken::Text(bus_str.to_string()); + + let t_str = match self.t_cycle { + TCycle::Tinit => "Tx", + TCycle::Ti => "Ti", + TCycle::T1 => "T1", + TCycle::T2 => "T2", + TCycle::T3 => "T3", + TCycle::T4 => "T4", + TCycle::Tw => "Tw", + }; + let t_str_token = SyntaxToken::Text(t_str.to_string()); + + let is_reading = self.i8288.mrdc | self.i8288.iorc; + let is_writing = self.i8288.mwtc | self.i8288.iowc; + + let mut xfer_str = " ".to_string(); + if is_reading { + xfer_str = format!("<-r {:02X}", self.data_bus); + } + else if is_writing { + xfer_str = format!("w-> {:02X}", self.data_bus); + } + + // Handle queue activity + + let mut q_read_str = " ".to_string(); + + let mut instr_str = String::new(); + + if self.last_queue_op == QueueOp::First || self.last_queue_op == QueueOp::Subsequent { + // Queue byte was read. + q_read_str = format!("<-q {:02X}", self.last_queue_byte); + } + let q_read_token = SyntaxToken::Text(q_read_str.to_string()); + + if self.last_queue_op == QueueOp::First { + // First byte of opcode read from queue. Decode the full instruction + instr_str = format!( + "[{:04X}:{:04X}] {} ({}) ", + self.cs, self.instruction_ip, self.i, self.i.size + ); + } + let instr_str_token = SyntaxToken::Text(instr_str.to_string()); + + let _dma_dreq_chr = match self.dma_aen { + true => 'R', + false => '.', + }; + + let tx_cycle = match self.is_last_wait() { + true => 'x', + false => '.', + }; + + let ready_chr = if self.wait_states > 0 { '.' } else { 'R' }; + + let dma_count_str = &format!("{:02} {:02}", dma_count, self.dram_refresh_cycle_num); + + let dma_str = match self.dma_state { + DmaState::Idle => dma_count_str, + DmaState::Dreq => "DREQ", + DmaState::Hrq => "HRQ ", + DmaState::HoldA => "HLDA", + DmaState::Operating(n) => match n { + 4 => "S1", + 3 => "S2", + 2 => "S3", + 1 => "S4", + _ => "S?", + }, //DmaState::DmaWait(..) => "DMAW" + DmaState::End => "END", + }; + let _dma_str_token = SyntaxToken::Text(dma_str.to_string()); + + let mut comment_str = String::new(); + for c in &self.trace_comment { + comment_str.push_str(&format!("; {}", c)); + } + + let bus_signal_token = SyntaxToken::Text(format!( + "M:{}{}{} I:{}{}{}", + rs_chr, aws_chr, ws_chr, ior_chr, aiow_chr, iow_chr + )); + + let token_vec = vec![ + SyntaxToken::Text(format!("{:04}", self.cycle_num)), + SyntaxToken::Text(format!("{:04}", self.instr_cycle)), + ale_token, + SyntaxToken::Text(format!("{:05X}", self.address_bus)), + seg_token, + SyntaxToken::Text(ready_chr.to_string()), + SyntaxToken::Text(self.wait_states.to_string()), + SyntaxToken::Text(tx_cycle.to_string()), + bus_signal_token, + SyntaxToken::Text(dma_str.to_string()), + bus_str_token, + t_str_token, + SyntaxToken::Text(xfer_str), + SyntaxToken::Text(format!("{:?}", self.fetch_state)), + q_op_token, + SyntaxToken::Text(self.last_queue_len.to_string()), + SyntaxToken::Text(self.queue.to_string()), + q_read_token, + instr_str_token, + SyntaxToken::Text(comment_str), + ]; + + token_vec + } + + pub fn cycle_table_header(&self) -> Vec { + vec![ + "Cycle".to_string(), + "icyc".to_string(), + "ALE".to_string(), + "Addr ".to_string(), + "Seg".to_string(), + "Rdy".to_string(), + "WS".to_string(), + "Tx".to_string(), + "8288 ".to_string(), + "DMA ".to_string(), + "Bus ".to_string(), + "T ".to_string(), + "Xfer ".to_string(), + "BIU".to_string(), + "Fetch ".to_string(), + "Qop".to_string(), + "Ql".to_string(), + "Queue ".to_string(), + "Qrd ".to_string(), + "Instr ".to_string(), + "Comments".to_string(), + ] + } + + pub fn flags_string(f: u16) -> String { + let c_chr = if CPU_FLAG_CARRY & f != 0 { 'C' } else { 'c' }; + let p_chr = if CPU_FLAG_PARITY & f != 0 { 'P' } else { 'p' }; + let a_chr = if CPU_FLAG_AUX_CARRY & f != 0 { 'A' } else { 'a' }; + let z_chr = if CPU_FLAG_ZERO & f != 0 { 'Z' } else { 'z' }; + let s_chr = if CPU_FLAG_SIGN & f != 0 { 'S' } else { 's' }; + let t_chr = if CPU_FLAG_TRAP & f != 0 { 'T' } else { 't' }; + let i_chr = if CPU_FLAG_INT_ENABLE & f != 0 { 'I' } else { 'i' }; + let d_chr = if CPU_FLAG_DIRECTION & f != 0 { 'D' } else { 'd' }; + let o_chr = if CPU_FLAG_OVERFLOW & f != 0 { 'O' } else { 'o' }; + + format!( + "1111{}{}{}{}{}{}0{}0{}1{}", + o_chr, d_chr, i_chr, t_chr, s_chr, z_chr, a_chr, p_chr, c_chr + ) + } + + /// Convert the two slots returned by get_pl_slots() into pairs of (bus, t-state) strings for display. + pub fn get_pl_slot_strings(&self) -> (String, String, String, String) { + let mut slot0_bus_str = String::from(" "); + let mut slot0_t_str = String::from(" "); + + let mut slot1_bus_str = String::from(" "); + let mut slot1_t_str = String::from(" "); + + let pl_slots = self.get_pl_slots(); + + match pl_slots[0] { + Some(BusSlotStatus::SlotA(status, cycle)) => { + slot0_bus_str = format!("{:04}", status); + slot0_t_str = format!("{:02}", cycle); + } + Some(BusSlotStatus::SlotB(status, cycle)) => { + slot0_bus_str = format!("{:04}", status); + slot0_t_str = format!("{:02}", cycle); + } + None => {} + } + match pl_slots[1] { + Some(BusSlotStatus::SlotA(status, cycle)) => { + slot1_bus_str = format!("{:04}", status); + slot1_t_str = format!("{:02}", cycle); + } + Some(BusSlotStatus::SlotB(status, cycle)) => { + slot1_bus_str = format!("{:04}", status); + slot1_t_str = format!("{:02}", cycle); + } + None => {} + } + + (slot0_bus_str, slot0_t_str, slot1_bus_str, slot1_t_str) + } + + /// Internally, we don't use pipeline slots to model the pipeline state. But visualizing the + /// bus states and t-cycles as two separate pipelines is more clear in logs. This function + /// splits the bus states into two slots based on the current pipeline slot flag which should + /// be toggled every time we enter a Tr cycle. + pub fn get_pl_slots(&self) -> [Option; 2] { + let mut slots = [None, None]; + + // Scenario 1: T cycle is Ti, Ta cycle is inactive. Always emit Ti in slot 0. + if self.t_cycle == TCycle::Ti && self.ta_cycle == TaCycle::Td { + slots[0] = Some(BusSlotStatus::SlotA(self.bus_status_latch, self.t_cycle)); + slots[1] = None; + return slots; + } + // Scenario 2: T cycle is Ti, Ta cycle is valid. Emit Ta in slot 0. + if self.t_cycle == TCycle::Ti && self.ta_cycle != TaCycle::Td { + slots[0] = Some(BusSlotStatus::SlotB(self.bus_status_latch, self.ta_cycle)); + slots[1] = None; + return slots; + } + // Scenario 3: T cycle is T1-T4, Ta cycle is inactive. Emit T cycle in pl_slot. + if self.t_cycle != TCycle::Ti && self.ta_cycle == TaCycle::Td { + slots[self.pl_slot as usize] = Some(BusSlotStatus::SlotA(self.bus_status_latch, self.t_cycle)); + slots[(!self.pl_slot) as usize] = None; + return slots; + } + // Scenario 4: T cycle is T1-T4, Ta cycle is valid. Emit the Ta cycle in pl_slot, T cycle in the other. + if self.t_cycle != TCycle::Ti && self.ta_cycle != TaCycle::Td { + slots[self.pl_slot as usize] = Some(BusSlotStatus::SlotB(self.pl_status, self.ta_cycle)); + slots[(!self.pl_slot) as usize] = Some(BusSlotStatus::SlotA(self.bus_status_latch, self.t_cycle)); + return slots; + } + panic!("Unhandled pl_slot scenario in get_pl_slots()"); + } +} diff --git a/core/src/cpu_vx0/microcode.rs b/core/src/cpu_vx0/microcode.rs new file mode 100644 index 00000000..2480b217 --- /dev/null +++ b/core/src/cpu_vx0/microcode.rs @@ -0,0 +1,40 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::microcode.rs + + Provides disassembly of each line of the 8088 microcode. + Used for debug printing when cycle tracing is enabled. + + Microcode disassembly by reenigne: + https://www.reenigne.org/blog/8086-microcode-disassembled/ + +*/ + +pub const MC_NONE: u16 = 0x200; +pub const MC_JUMP: u16 = 0x201; +pub const MC_RTN: u16 = 0x202; +pub const MC_CORR: u16 = 0x203; diff --git a/core/src/cpu_vx0/mnemonic.rs b/core/src/cpu_vx0/mnemonic.rs new file mode 100644 index 00000000..1086d8f6 --- /dev/null +++ b/core/src/cpu_vx0/mnemonic.rs @@ -0,0 +1,31 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::mnemonic.rs + + Defines mnemonic enum. + +*/ diff --git a/core/src/cpu_vx0/mod.rs b/core/src/cpu_vx0/mod.rs new file mode 100644 index 00000000..bf443e0c --- /dev/null +++ b/core/src/cpu_vx0/mod.rs @@ -0,0 +1,1932 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::mod.rs + + Implements the 8088 (And eventually 8086) CPU. + +*/ + +#![allow(clippy::unusual_byte_groupings)] + +// Pull in all CPU module components +mod addressing; +mod alu; +mod bcd; +mod bitwise; +mod biu; +mod cpu; +mod cycle; +mod decode; +mod display; +mod execute; +mod fuzzer; +mod gdr; +mod instruction; +mod interrupt; +mod jump; +mod logging; +mod microcode; +pub mod mnemonic; +mod modrm; +mod muldiv; +mod queue; +mod stack; +mod step; +mod string; + +use crate::cpu_common::QueueOp; +use core::fmt::Display; +use lazy_static::lazy_static; +use regex::Regex; +use std::{collections::VecDeque, error::Error, fmt, path::Path}; + +pub use crate::cpu_common::Cpu; + +use crate::{ + breakpoints::{BreakPointType, CycleStopWatch, StopWatchData}, + bus::{BusInterface, MEM_BPA_BIT, MEM_BPE_BIT, MEM_RET_BIT, MEM_SW_BIT}, + bytequeue::*, + cpu_common::{ + instruction::Instruction, + CpuAddress, + CpuOption, + CpuStringState, + CpuSubType, + CpuType, + ExecutionResult, + Mnemonic, + Segment, + TraceMode, + }, + cpu_vx0::{microcode::*, queue::InstructionQueue}, + syntax_token::*, + tracelogger::TraceLogger, +}; + +// Make ReadWriteFlag available to benchmarks +pub use crate::cpu_vx0::biu::ReadWriteFlag; + +#[cfg(feature = "cpu_validator")] +use crate::cpu_validator::ValidatorType; + +#[cfg(feature = "cpu_validator")] +use crate::cpu_validator::{ + AccessType, + BusCycle, + BusState, + CpuValidator, + CycleState, + VRegisters, + ValidatorMode, + ValidatorResult, + VAL_ALLOW_ONE, + VAL_NO_CYCLES, + VAL_NO_FLAGS, + VAL_NO_WRITES, +}; + +#[cfg(feature = "arduino_validator")] +use crate::arduino8088_validator::ArduinoValidator; + +macro_rules! trace_print { + ($self:ident, $($t:tt)*) => {{ + if $self.trace_enabled { + if let TraceMode::CycleText = $self.trace_mode { + $self.trace_print(&format!($($t)*)); + } + } + }}; +} + +#[macro_export] +macro_rules! vgdr { + ($inst:expr) => { + &DECODE[$inst.decode_idx as usize].gdr + }; +} + +use crate::cpu_common::{operands::OperandSize, Register16, Register8, ServiceEvent}; +use trace_print; + +const QUEUE_MAX: usize = 6; +const FETCH_DELAY: u8 = 2; + +const CPU_HISTORY_LEN: usize = 32; +const CPU_CALL_STACK_LEN: usize = 48; + +const INTERRUPT_VEC_LEN: usize = 4; +const INTERRUPT_BREAKPOINT: u8 = 1; + +pub const CPU_FLAG_CARRY: u16 = 0b0000_0000_0000_0001; +pub const CPU_FLAG_RESERVED1: u16 = 0b0000_0000_0000_0010; +pub const CPU_FLAG_PARITY: u16 = 0b0000_0000_0000_0100; +pub const CPU_FLAG_RESERVED3: u16 = 0b0000_0000_0000_1000; +pub const CPU_FLAG_AUX_CARRY: u16 = 0b0000_0000_0001_0000; +pub const CPU_FLAG_RESERVED5: u16 = 0b0000_0000_0010_0000; +pub const CPU_FLAG_ZERO: u16 = 0b0000_0000_0100_0000; +pub const CPU_FLAG_SIGN: u16 = 0b0000_0000_1000_0000; +pub const CPU_FLAG_TRAP: u16 = 0b0000_0001_0000_0000; +pub const CPU_FLAG_INT_ENABLE: u16 = 0b0000_0010_0000_0000; +pub const CPU_FLAG_DIRECTION: u16 = 0b0000_0100_0000_0000; +pub const CPU_FLAG_OVERFLOW: u16 = 0b0000_1000_0000_0000; + +/* +const CPU_FLAG_RESERVED12: u16 = 0b0001_0000_0000_0000; +const CPU_FLAG_RESERVED13: u16 = 0b0010_0000_0000_0000; +const CPU_FLAG_RESERVED14: u16 = 0b0100_0000_0000_0000; +const CPU_FLAG_RESERVED15: u16 = 0b1000_0000_0000_0000; +*/ + +const CPU_FLAGS_RESERVED_ON: u16 = 0b1111_0000_0000_0010; +const CPU_FLAGS_RESERVED_OFF: u16 = !(CPU_FLAG_RESERVED3 | CPU_FLAG_RESERVED5); + +const FLAGS_POP_MASK: u16 = 0b0000_1111_1101_0101; + +const REGISTER_HI_MASK: u16 = 0b0000_0000_1111_1111; +const REGISTER_LO_MASK: u16 = 0b1111_1111_0000_0000; + +pub const MAX_INSTRUCTION_SIZE: usize = 15; + +const OPCODE_REGISTER_SELECT_MASK: u8 = 0b0000_0111; + +// Instruction flags +const I_USES_MEM: u32 = 0b0000_0001; // Instruction has a memory operand +const I_HAS_MODRM: u32 = 0b0000_0010; // Instruction has a modrm byte +const I_LOCKABLE: u32 = 0b0000_0100; // Instruction compatible with LOCK prefix +const I_REL_JUMP: u32 = 0b0000_1000; +const I_LOAD_EA: u32 = 0b0001_0000; // Instruction loads from its effective address +const I_GROUP_DELAY: u32 = 0b0010_0000; // Instruction has cycle delay for being a specific group instruction + +// Instruction prefixes +pub const OPCODE_PREFIX_ES_OVERRIDE: u32 = 0b_0000_0000_0001; +pub const OPCODE_PREFIX_CS_OVERRIDE: u32 = 0b_0000_0000_0010; +pub const OPCODE_PREFIX_SS_OVERRIDE: u32 = 0b_0000_0000_0100; +pub const OPCODE_PREFIX_DS_OVERRIDE: u32 = 0b_0000_0000_1000; +pub const OPCODE_SEG_OVERRIDE_MASK: u32 = 0b_0000_0000_1111; +pub const OPCODE_PREFIX_WAIT: u32 = 0b_0000_0100_0000; +pub const OPCODE_PREFIX_LOCK: u32 = 0b_0000_1000_0000; +pub const OPCODE_PREFIX_REP1: u32 = 0b_0001_0000_0000; +pub const OPCODE_PREFIX_REP2: u32 = 0b_0010_0000_0000; + +// The parity flag is calculated from the lower 8 bits of an alu operation regardless +// of the operand width. It is trivial to precalculate an 8-bit parity table. +pub const PARITY_TABLE: [bool; 256] = { + let mut table = [false; 256]; + let mut index = 0; + loop { + table[index] = index.count_ones() % 2 == 0; + index += 1; + + if index == 256 { + break; + } + } + table +}; + +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct GeneralRegisterBytes { + pub l: u8, + pub h: u8, +} + +#[repr(C)] +pub union GeneralRegister { + b: GeneralRegisterBytes, + w: u16, +} +impl Default for GeneralRegister { + fn default() -> Self { + GeneralRegister { w: 0 } + } +} + +impl GeneralRegister { + // Safety: It is safe to access fields of a union comprised of unsigned integer types. + #[inline(always)] + pub fn x(&self) -> u16 { + unsafe { self.w } + } + #[inline(always)] + pub fn set_x(&mut self, value: u16) { + self.w = value; + } + #[inline(always)] + pub fn incr_x(&mut self) { + self.w = unsafe { self.w.wrapping_add(1) }; + } + #[inline(always)] + pub fn decr_x(&mut self) { + self.w = unsafe { self.w.wrapping_sub(1) }; + } + #[inline(always)] + pub fn h(&self) -> u8 { + unsafe { self.b.h } + } + #[inline(always)] + pub fn set_h(&mut self, value: u8) { + self.b.h = value; + } + #[inline(always)] + pub fn incr_h(&mut self) { + self.b.h = unsafe { self.b.h.wrapping_add(1) }; + } + #[inline(always)] + pub fn decr_h(&mut self) { + self.b.h = unsafe { self.b.h.wrapping_sub(1) }; + } + #[inline(always)] + pub fn l(&self) -> u8 { + unsafe { self.b.l } + } + #[inline(always)] + pub fn set_l(&mut self, value: u8) { + self.b.l = value; + } + #[inline(always)] + pub fn incr_l(&mut self) { + self.b.l = unsafe { self.b.l.wrapping_add(1) }; + } + #[inline(always)] + pub fn decr_l(&mut self) { + self.b.l = unsafe { self.b.l.wrapping_sub(1) }; + } +} + +pub const REGISTER16_LUT: [Register16; 8] = [ + Register16::AX, + Register16::CX, + Register16::DX, + Register16::BX, + Register16::SP, + Register16::BP, + Register16::SI, + Register16::DI, +]; + +pub const SEGMENT_REGISTER16_LUT: [Register16; 4] = [Register16::ES, Register16::CS, Register16::SS, Register16::DS]; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum CpuState { + Normal, + BreakpointHit, +} +impl Default for CpuState { + fn default() -> Self { + CpuState::Normal + } +} + +#[derive(Copy, Clone, Debug)] +pub enum CallStackEntry { + Call { + ret_cs: u16, + ret_ip: u16, + call_ip: u16, + }, + CallF { + ret_cs: u16, + ret_ip: u16, + call_cs: u16, + call_ip: u16, + }, + Interrupt { + ret_cs: u16, + ret_ip: u16, + call_cs: u16, + call_ip: u16, + itype: InterruptType, + number: u8, + ah: u8, + }, +} + +/// Representation of a flag in the eFlags CPU register +pub enum Flag { + Carry, + Parity, + AuxCarry, + Zero, + Sign, + Trap, + Interrupt, + Direction, + Overflow, +} + +/* +pub enum Register { + AH, + AL, + AX, + BH, + BL, + BX, + CH, + CL, + CX, + DH, + DL, + DX, + SP, + BP, + SI, + DI, + CS, + DS, + SS, + ES, + IP, +}*/ + +#[derive(Copy, Clone, Default, Debug)] +pub enum DmaState { + #[default] + Idle, + Dreq, + Hrq, + HoldA, + Operating(u8), + End, + //DmaWait(u8) +} + +#[derive(Default, Debug)] +pub enum RepType { + #[default] + NoRep, + Rep, + Repne, + Repe, + MulDiv, +} + +#[allow(dead_code)] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum InterruptType { + NMI, + Exception, + Software, + Hardware, +} + +#[derive(Copy, Clone, Default, Debug, PartialEq)] +pub enum BusPendingType { + #[default] + None, + EuEarly, + EuLate, +} + +#[derive(Copy, Clone, Default, Debug, PartialEq)] +pub enum FetchState { + #[default] + Normal, + PausedFull, + Delayed(u8), + Suspended, + Halted, +} + +pub enum HistoryEntry { + InstructionEntry { + cs: u16, + ip: u16, + cycles: u16, + interrupt: bool, + jump: bool, + i: Instruction, + }, + InterruptEntry { + cs: u16, + ip: u16, + cycles: u16, + iv: u8, + }, + NmiEntry { + cs: u16, + ip: u16, + }, + TrapEntry { + cs: u16, + ip: u16, + }, +} + +#[derive(Copy, Clone)] +pub struct InterruptDescriptor { + itype: InterruptType, + number: u8, + ah: u8, +} + +impl Default for InterruptDescriptor { + fn default() -> Self { + InterruptDescriptor { + itype: InterruptType::Hardware, + number: 0, + ah: 0, + } + } +} + +#[derive(Copy, Clone, Debug)] +pub enum TransferSize { + Byte, + Word, +} + +impl Default for TransferSize { + fn default() -> TransferSize { + TransferSize::Byte + } +} + +#[derive(Default)] +pub struct I8288 { + // Command bus + mrdc: bool, + amwc: bool, + mwtc: bool, + iorc: bool, + aiowc: bool, + iowc: bool, + inta: bool, + // Control output + _dtr: bool, + ale: bool, + _pden: bool, + _den: bool, +} + +#[derive(Default)] +pub struct NecVx0 { + cpu_type: CpuType, + state: CpuState, + + a: GeneralRegister, + b: GeneralRegister, + c: GeneralRegister, + d: GeneralRegister, + sp: u16, + bp: u16, + si: u16, + di: u16, + cs: u16, + ds: u16, + ss: u16, + es: u16, + //ip: u16, + flags: u16, + + address_bus: u32, + address_latch: u32, + data_bus: u16, + last_ea: u16, // Last calculated effective address. Used by 0xFE instructions + bus: BusInterface, // CPU owns Bus + i8288: I8288, // Intel 8288 Bus Controller + pc: u16, // Program counter points to the next instruction to be fetched + nx: bool, + rni: bool, + ea_opr: u16, // Operand loaded by EALOAD. Masked to 8 bits as appropriate. + + intr: bool, // State of INTR line + intr_pending: bool, // INTR line active and not processed + in_int: bool, + int_count: u64, + iret_count: u64, + interrupt_inhibit: bool, + + // Operand and result state + /* + op1_8: u8, + op1_16: u16, + op2_8: u8, + op2_16: u16, + result_8: u8, + result_16: u16, + */ + // BIU stuff + ready: bool, // READY line from 8284 + queue: InstructionQueue, + fetch_size: TransferSize, + fetch_state: FetchState, + bus_pending: BusPendingType, // Has the EU requested a bus operation? + queue_op: QueueOp, + last_queue_op: QueueOp, + queue_byte: u8, + last_queue_byte: u8, + last_queue_len: usize, + t_cycle: TCycle, // The current cycle of the main bus cycle (T1-T4) + ta_cycle: TaCycle, // The current cycle of the bus address cycle (Tr-T0) + bus_status: BusStatus, // The current bus status type, valid on T1-T2. + bus_status_latch: BusStatus, // The current bus status type, valid on T1-T4. + pl_status: BusStatus, // The upcoming (pipelined) bus status type. + pl_slot: bool, + bus_segment: Segment, + transfer_size: TransferSize, // Width of current bus transfer + operand_size: OperandSize, // Width of the operand being transferred + transfer_n: u32, // Current transfer number (Either 1 or 2, for byte or word operand, respectively) + final_transfer: bool, // Flag that determines if the current bus transfer is the final transfer for this bus request + bus_wait_states: u32, + wait_states: u32, + lock: bool, // LOCK pin. Asserted during 2nd INTA bus cycle. + + // Halt-related stuff + halted: bool, + reported_halt: bool, // Only error on halt once. The caller can determine if it wants to continue. + halt_not_hold: bool, // Internal halt signal + wake_timer: u32, + + is_running: bool, + is_error: bool, + + // Rep prefix handling + in_rep: bool, + rep_init: bool, + rep_mnemonic: Mnemonic, + rep_type: RepType, + + cycle_num: u64, + halt_cycles: u64, + t_stamp: f64, + t_step: f64, + t_step_h: f64, + instr_cycle: u32, + device_cycles: u32, + int_elapsed: u32, + instr_elapsed: u32, + instruction_count: u64, + i: Instruction, // Currently executing instruction + instruction_ip: u16, + instruction_reentrant: bool, // Is an instruction reentrant? (REP stringop, HLT) + last_cs: u16, + last_ip: u16, + last_intr: bool, + jumped: bool, + instruction_address: u32, + instruction_history_on: bool, + instruction_history: VecDeque, + call_stack: VecDeque, + exec_result: ExecutionResult, + + // Breakpoints + breakpoints: Vec, + stopwatches: Vec>, + stopwatch_running: bool, + step_over_target: Option, + step_over_breakpoint: Option, + + reset_vector: CpuAddress, + + enable_service_interrupt: bool, + trace_enabled: bool, + trace_mode: TraceMode, + trace_logger: TraceLogger, + trace_comment: Vec<&'static str>, + trace_instr: u16, + trace_str_vec: Vec, + trace_token_vec: Vec>, + + enable_wait_states: bool, + off_rails_detection: bool, + opcode0_counter: u32, + + rng: Option, + + #[cfg(feature = "cpu_validator")] + validator: Option>, + #[cfg(feature = "cpu_validator")] + vregs: VRegisters, + #[cfg(feature = "cpu_validator")] + cycle_states: Vec, + #[cfg(feature = "cpu_validator")] + validator_state: CpuValidatorState, + #[cfg(feature = "cpu_validator")] + validator_end: usize, + #[cfg(feature = "cpu_validator")] + peek_fetch: u8, + #[cfg(feature = "cpu_validator")] + instr_slice: Vec, + + end_addr: usize, + + service_events: VecDeque, + + // Interrupt scheduling + interrupt_scheduling: bool, + interrupt_cycle_period: u32, + interrupt_cycle_num: u32, + interrupt_retrigger: bool, + + clk0: bool, + + // DMA stuff + dma_state: DmaState, + dram_refresh_simulation: bool, + dram_refresh_cycle_period: u32, + dram_refresh_cycle_num: u32, + dram_refresh_adjust: u32, + dram_refresh_tc: bool, + dram_refresh_retrigger: bool, + dma_aen: bool, + dma_holda: bool, + dma_req: bool, + dma_ack: bool, + dma_wait_states: u32, + + // Trap stuff + trap_enable_delay: u32, // Number of cycles to delay trap flag enablement. + trap_disable_delay: u32, // Number of cycles to delay trap flag disablement. + trap_suppressed: bool, // Suppress trap handling for the last executed instruction. + + nmi: bool, // Status of NMI line. + nmi_triggered: bool, // Has NMI been edge-triggered? + + halt_resume_delay: u32, + int_flags: Vec, +} + +#[cfg(feature = "cpu_validator")] +#[derive(PartialEq, Copy, Clone)] +pub enum CpuValidatorState { + Uninitialized, + Running, + Hung, + Ended, +} + +#[cfg(feature = "cpu_validator")] +impl Default for CpuValidatorState { + fn default() -> Self { + CpuValidatorState::Uninitialized + } +} + +pub struct CpuRegisterState { + pub ah: u8, + pub al: u8, + pub ax: u16, + pub bh: u8, + pub bl: u8, + pub bx: u16, + pub ch: u8, + pub cl: u8, + pub cx: u16, + pub dh: u8, + pub dl: u8, + pub dx: u16, + pub sp: u16, + pub bp: u16, + pub si: u16, + pub di: u16, + pub cs: u16, + pub ds: u16, + pub ss: u16, + pub es: u16, + pub pc: u16, + pub ip: u16, + pub flags: u16, +} + +/// The 8088 has a 7-cycle bus access time. 3 of these cycles can be pipelined during the previous +/// bus cycle. These cycles can alternatively be considered an 'address cycle'. +/// Tr: The cycle on which the EU or prefetcher requests a bus cycle +/// Ts: The first cycle of address calculation +/// T0: The last cycle of address calculation (may repeat) +/// Td: No address calculation in progress (done) +/// https://martypc.blogspot.com/2024/02/the-complete-bus-logic-of-intel-8088.html +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub enum TaCycle { + #[default] + Tr, // T-Request. This is the cycle on which the EU or prefetcher requests a bus cycle. + Ts, // T-Start. This is the cycle on which the first cycle of address calculation occurs. + T0, // T-Zero. This is the cycle on which address calculation completes. T0 may be repeated. + Td, // T-done. Not a real cycle state, but indicates that no address cycle is in progress. + Ta, // T-abort. Signals that the address cycle has been aborted. +} + +impl Display for TaCycle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TaCycle::Tr => write!(f, "Tr"), + TaCycle::Ts => write!(f, "Ts"), + TaCycle::T0 => write!(f, "T0"), + TaCycle::Td => write!(f, " "), + TaCycle::Ta => write!(f, "Ta"), + } + } +} + +/// The traditional bus cycle model of the 8088 includes T-states from T1 to T4. The CPU executes +/// 'Ti' or idle T-states when not in an active bus transaction. +/// Tinit is not a real T-cycle but a state that indicates a new bus cycle has just been initiated +/// and should be moved to a valid state. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub enum TCycle { + Tinit, + #[default] + Ti, + T1, + T2, + T3, + Tw, + T4, +} + +impl Display for TCycle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TCycle::Tinit => write!(f, "Tx"), + TCycle::Ti => write!(f, "Ti"), + TCycle::T1 => write!(f, "T1"), + TCycle::T2 => write!(f, "T2"), + TCycle::T3 => write!(f, "T3"), + TCycle::Tw => write!(f, "Tw"), + TCycle::T4 => write!(f, "T4"), + } + } +} + +/// The 8088 has 8 possible bus cycle types. These are advertised as an octal value on CPU status +/// pins S0-S2 in Maximum mode. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub enum BusStatus { + InterruptAck = 0, // IRQ Acknowledge + IoRead = 1, // IO Read + IoWrite = 2, // IO Write + Halt = 3, // Halt + CodeFetch = 4, // Code Access + MemRead = 5, // Memory Read + MemWrite = 6, // Memory Write + #[default] + Passive = 7, // Passive +} + +impl Display for BusStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BusStatus::InterruptAck => write!(f, "INTA"), + BusStatus::IoRead => write!(f, "IOR "), + BusStatus::IoWrite => write!(f, "IOW "), + BusStatus::Halt => write!(f, "HALT"), + BusStatus::CodeFetch => write!(f, "CODE"), + BusStatus::MemRead => write!(f, "MEMR"), + BusStatus::MemWrite => write!(f, "MEMW"), + BusStatus::Passive => write!(f, "PASV"), + } + } +} + +impl NecVx0 { + pub fn new( + cpu_type: CpuType, + trace_mode: TraceMode, + trace_logger: TraceLogger, + #[cfg(feature = "cpu_validator")] validator_type: ValidatorType, + #[cfg(feature = "cpu_validator")] validator_trace: TraceLogger, + #[cfg(feature = "cpu_validator")] validator_mode: ValidatorMode, + #[cfg(feature = "cpu_validator")] validator_baud: u32, + ) -> Self { + let mut cpu: NecVx0 = Default::default(); + + match cpu_type { + CpuType::NecV20 => { + cpu.queue.set_size(4, 1); + cpu.fetch_size = TransferSize::Byte; + } + /* + CpuType::NecV30 => { + cpu.queue.set_size(6, 2); + cpu.fetch_size = TransferSize::Word; + }*/ + _ => { + panic!("Invalid CPU subtype.") + } + } + + #[cfg(feature = "cpu_validator")] + { + cpu.validator = match validator_type { + #[cfg(feature = "arduino_validator")] + ValidatorType::Arduino8088 => Some(Box::new(ArduinoValidator::new(validator_trace, validator_baud))), + _ => None, + }; + + if let Some(ref mut validator) = cpu.validator { + match validator.init(validator_mode, true, true, false) { + true => {} + false => { + panic!("Failed to init cpu validator."); + } + } + } + } + + cpu.trace_logger = trace_logger; + cpu.trace_mode = trace_mode; + cpu.cpu_type = cpu_type; + + //cpu.instruction_history_on = true; // Control this from config/GUI instead + cpu.instruction_history = VecDeque::with_capacity(16); + + cpu.reset_vector = CpuAddress::Segmented(0xFFFF, 0x0000); + cpu.reset(); + cpu + } + + pub fn get_instruction_ct(&self) -> u64 { + self.instruction_count + } + + /// Calculate the value of IP as needed. The IP register on the 808X is not a physical register, + /// but produced on demand by adjusting PC by the size of the queue. + #[inline] + pub fn ip(&self) -> u16 { + self.pc.wrapping_sub(self.queue.len_p() as u16) + } + + #[inline] + pub fn ip_v(&self) -> u16 { + self.pc.wrapping_sub(self.queue.len() as u16) + } + + /// Return the IP value for disassembly purposes. We wish to adjust the real value of IP in + /// certain circumstances, such as when a reentrant instruction is being executed. This is so + /// that reentrant instructions stay in the disassembly viewer until completed. + pub fn disassembly_ip(&self) -> u16 { + if self.instruction_reentrant { + self.pc.wrapping_sub(self.queue.len_p() as u16 + self.i.size as u16) + } + else { + self.pc.wrapping_sub(self.queue.len_p() as u16) + } + } + + pub fn flat_sp(&self) -> u32 { + NecVx0::calc_linear_address(self.ss, self.sp) + } + + /// Execute the CORR (Correct PC) microcode routine. + /// This is used to correct the PC in anticipation of a jump or call, where the queue will + /// be flushed. Unlike most microcode instructions, CORR takes two cycles to execute. + #[inline] + pub fn corr(&mut self) { + self.pc = self.pc.wrapping_sub(self.queue.len() as u16); + self.cycle_i(MC_CORR); + } + + #[allow(dead_code)] + pub fn in_rep(&self) -> bool { + self.in_rep + } + + pub fn get_csip(&self) -> CpuAddress { + CpuAddress::Segmented(self.cs, self.ip()) + } + + #[inline] + pub fn is_last_wait_t3tw(&self) -> bool { + self.wait_states == 0 && self.dma_wait_states == 0 + } + + #[inline] + pub fn is_last_wait(&self) -> bool { + match self.t_cycle { + TCycle::T3 | TCycle::Tw => { + if self.wait_states == 0 && self.dma_wait_states == 0 { + true + } + else { + false + } + } + _ => false, + } + } + + #[inline] + pub fn is_before_last_wait(&self) -> bool { + match self.t_cycle { + TCycle::T1 | TCycle::T2 => true, + TCycle::T3 | TCycle::Tw => { + if self.wait_states > 0 || self.dma_wait_states > 0 { + true + } + else { + false + } + } + _ => false, + } + } + + #[inline] + pub fn is_before_t3(&self) -> bool { + match self.t_cycle { + TCycle::T1 | TCycle::T2 => true, + _ => false, + } + } + + #[cfg(feature = "cpu_validator")] + pub fn get_cycle_state(&mut self) -> CycleState { + let mut q = [0; 4]; + self.queue.to_slice(&mut q); + + CycleState { + n: self.instr_cycle, + addr: self.address_latch, + t_state: match self.t_cycle { + TCycle::Tinit | TCycle::T1 => BusCycle::T1, + TCycle::Ti => BusCycle::T1, + TCycle::T2 => BusCycle::T2, + TCycle::T3 => BusCycle::T3, + TCycle::Tw => BusCycle::Tw, + TCycle::T4 => BusCycle::T4, + }, + a_type: match self.bus_segment { + Segment::ES => AccessType::AlternateData, + Segment::SS => AccessType::Stack, + Segment::DS => AccessType::Data, + Segment::None | Segment::CS => AccessType::CodeOrNone, + }, + // TODO: Unify these enums? + b_state: match self.t_cycle { + TCycle::T1 | TCycle::T2 => match self.bus_status_latch { + BusStatus::InterruptAck => BusState::INTA, + BusStatus::IoRead => BusState::IOR, + BusStatus::IoWrite => BusState::IOW, + BusStatus::Halt => BusState::HALT, + BusStatus::CodeFetch => BusState::CODE, + BusStatus::MemRead => BusState::MEMR, + BusStatus::MemWrite => BusState::MEMW, + BusStatus::Passive => BusState::PASV, + }, + _ => BusState::PASV, + }, + ale: self.i8288.ale, + mrdc: !self.i8288.mrdc, + amwc: !self.i8288.amwc, + mwtc: !self.i8288.mwtc, + iorc: !self.i8288.iorc, + aiowc: !self.i8288.aiowc, + iowc: !self.i8288.iowc, + inta: !self.i8288.inta, + q_op: self.last_queue_op, + q_byte: self.last_queue_byte, + q_len: self.queue.len() as u32, + q, + data_bus: self.data_bus, + } + } + + pub fn is_error(&self) -> bool { + self.is_error + } + + pub fn set_nmi(&mut self, nmi_state: bool) { + if nmi_state == false { + self.nmi_triggered = false; + } + self.nmi = nmi_state; + } + + #[inline(always)] + pub fn set_flag(&mut self, flag: Flag) { + self.flags |= match flag { + Flag::Carry => CPU_FLAG_CARRY, + Flag::Parity => CPU_FLAG_PARITY, + Flag::AuxCarry => CPU_FLAG_AUX_CARRY, + Flag::Zero => CPU_FLAG_ZERO, + Flag::Sign => CPU_FLAG_SIGN, + Flag::Trap => CPU_FLAG_TRAP, + Flag::Interrupt => { + // Only inhibit interrupts if the interrupt flag was not previously set + if !self.get_flag(Flag::Interrupt) { + self.interrupt_inhibit = false; + } + CPU_FLAG_INT_ENABLE + } + Flag::Direction => CPU_FLAG_DIRECTION, + Flag::Overflow => CPU_FLAG_OVERFLOW, + }; + } + + #[inline(always)] + pub fn clear_flag(&mut self, flag: Flag) { + self.flags &= match flag { + Flag::Carry => !CPU_FLAG_CARRY, + Flag::Parity => !CPU_FLAG_PARITY, + Flag::AuxCarry => !CPU_FLAG_AUX_CARRY, + Flag::Zero => !CPU_FLAG_ZERO, + Flag::Sign => !CPU_FLAG_SIGN, + Flag::Trap => !CPU_FLAG_TRAP, + Flag::Interrupt => !CPU_FLAG_INT_ENABLE, + Flag::Direction => !CPU_FLAG_DIRECTION, + Flag::Overflow => !CPU_FLAG_OVERFLOW, + }; + } + + pub fn set_flags(&mut self, mut flags: u16) { + // Clear reserved 0 flags + flags &= CPU_FLAGS_RESERVED_OFF; + // Set reserved 1 flags + flags |= CPU_FLAGS_RESERVED_ON; + + self.flags = flags; + } + + #[inline(always)] + pub fn set_flag_state(&mut self, flag: Flag, state: bool) { + if state { + self.set_flag(flag) + } + else { + self.clear_flag(flag) + } + } + + pub fn store_flags(&mut self, bits: u16) { + // Clear SF, ZF, AF, PF & CF flags + let flag_mask = !(CPU_FLAG_CARRY | CPU_FLAG_PARITY | CPU_FLAG_AUX_CARRY | CPU_FLAG_ZERO | CPU_FLAG_SIGN); + self.flags &= flag_mask; + + // Copy flag state + self.flags |= bits & !flag_mask; + } + + pub fn load_flags(&mut self) -> u16 { + // Return 8 LO bits of flags register + self.flags & 0x00FF + } + + #[inline] + pub fn get_flag(&self, flag: Flag) -> bool { + self.flags + & match flag { + Flag::Carry => CPU_FLAG_CARRY, + Flag::Parity => CPU_FLAG_PARITY, + Flag::AuxCarry => CPU_FLAG_AUX_CARRY, + Flag::Zero => CPU_FLAG_ZERO, + Flag::Sign => CPU_FLAG_SIGN, + Flag::Trap => CPU_FLAG_TRAP, + Flag::Interrupt => CPU_FLAG_INT_ENABLE, + Flag::Direction => CPU_FLAG_DIRECTION, + Flag::Overflow => CPU_FLAG_OVERFLOW, + } + != 0 + } + + #[cfg(feature = "cpu_validator")] + pub fn get_vregisters(&self) -> VRegisters { + VRegisters { + ax: self.a.x(), + bx: self.b.x(), + cx: self.c.x(), + dx: self.d.x(), + cs: self.cs, + ss: self.ss, + ds: self.ds, + es: self.es, + sp: self.sp, + bp: self.bp, + si: self.si, + di: self.di, + ip: self.ip(), + flags: self.flags, + } + } + + /* + pub fn get_register(&self, reg: Register) -> RegisterType { + match reg { + Register::AH => RegisterType::Register8(self.ah), + Register::AL => RegisterType::Register8(self.al), + Register::AX => RegisterType::Register16(self.ax), + Register::BH => RegisterType::Register8(self.bh), + Register::BL => RegisterType::Register8(self.bl), + Register::BX => RegisterType::Register16(self.bx), + Register::CH => RegisterType::Register8(self.ch), + Register::CL => RegisterType::Register8(self.cl), + Register::CX => RegisterType::Register16(self.cx), + Register::DH => RegisterType::Register8(self.dh), + Register::DL => RegisterType::Register8(self.dl), + Register::DX => RegisterType::Register16(self.dx), + Register::SP => RegisterType::Register16(self.sp), + Register::BP => RegisterType::Register16(self.bp), + Register::SI => RegisterType::Register16(self.si), + Register::DI => RegisterType::Register16(self.di), + Register::CS => RegisterType::Register16(self.cs), + Register::DS => RegisterType::Register16(self.ds), + Register::SS => RegisterType::Register16(self.ss), + Register::ES => RegisterType::Register16(self.es), + _ => panic!("Invalid register") + } + } + */ + + #[inline] + pub fn get_register8(&self, reg: Register8) -> u8 { + match reg { + Register8::AH => self.a.h(), + Register8::AL => self.a.l(), + Register8::BH => self.b.h(), + Register8::BL => self.b.l(), + Register8::CH => self.c.h(), + Register8::CL => self.c.l(), + Register8::DH => self.d.h(), + Register8::DL => self.d.l(), + } + } + + #[inline] + pub fn get_register16(&self, reg: Register16) -> u16 { + match reg { + Register16::AX => self.a.x(), + Register16::BX => self.b.x(), + Register16::CX => self.c.x(), + Register16::DX => self.d.x(), + Register16::SP => self.sp, + Register16::BP => self.bp, + Register16::SI => self.si, + Register16::DI => self.di, + Register16::CS => self.cs, + Register16::DS => self.ds, + Register16::SS => self.ss, + Register16::ES => self.es, + Register16::PC => self.pc, + _ => panic!("Invalid register"), + } + } + + #[inline] + pub fn get_flags(&self) -> u16 { + self.flags + } + + // Set one of the 8 bit registers. + #[inline] + pub fn set_register8(&mut self, reg: Register8, value: u8) { + match reg { + Register8::AH => self.a.set_h(value), + Register8::AL => self.a.set_l(value), + Register8::BH => self.b.set_h(value), + Register8::BL => self.b.set_l(value), + Register8::CH => self.c.set_h(value), + Register8::CL => self.c.set_l(value), + Register8::DH => self.d.set_h(value), + Register8::DL => self.d.set_l(value), + } + } + + // Set one of the 16 bit registers. + #[inline] + pub fn set_register16(&mut self, reg: Register16, value: u16) { + match reg { + Register16::AX => self.a.set_x(value), + Register16::BX => self.b.set_x(value), + Register16::CX => self.c.set_x(value), + Register16::DX => self.d.set_x(value), + Register16::SP => self.sp = value, + Register16::BP => self.bp = value, + Register16::SI => self.si = value, + Register16::DI => self.di = value, + Register16::CS => self.cs = value, + Register16::DS => self.ds = value, + Register16::SS => self.ss = value, + Register16::ES => self.es = value, + Register16::PC => self.pc = value, + _ => panic!("bad register16"), + } + } + + /// Converts a Register8 into a Register16. + /// Only really useful for r forms of FE.03-07 which operate on 8 bits of a memory + /// operand but 16 bits of a register operand. We don't support 'hybrid' 8/16 bit + /// instruction templates, so we have to convert. + #[inline] + pub fn reg8to16(reg: Register8) -> Register16 { + match reg { + Register8::AH => Register16::AX, + Register8::AL => Register16::AX, + Register8::BH => Register16::BX, + Register8::BL => Register16::BX, + Register8::CH => Register16::CX, + Register8::CL => Register16::CX, + Register8::DH => Register16::DX, + Register8::DL => Register16::DX, + } + } + + #[inline] + pub fn decrement_register8(&mut self, reg: Register8) { + match reg { + Register8::AH => self.a.decr_h(), + Register8::AL => self.a.decr_l(), + Register8::BH => self.b.decr_h(), + Register8::BL => self.b.decr_l(), + Register8::CH => self.c.decr_h(), + Register8::CL => self.c.decr_l(), + Register8::DH => self.d.decr_h(), + Register8::DL => self.d.decr_l(), + } + } + + #[inline] + pub fn decrement_register16(&mut self, reg: Register16) { + match reg { + Register16::AX => self.a.decr_x(), + Register16::BX => self.b.decr_x(), + Register16::CX => self.c.decr_x(), + Register16::DX => self.d.decr_x(), + Register16::SP => self.sp = self.sp.wrapping_sub(1), + Register16::BP => self.bp = self.bp.wrapping_sub(1), + Register16::SI => self.si = self.si.wrapping_sub(1), + Register16::DI => self.di = self.di.wrapping_sub(1), + Register16::CS => self.cs = self.cs.wrapping_sub(1), + Register16::DS => self.ds = self.ds.wrapping_sub(1), + Register16::SS => self.ss = self.ss.wrapping_sub(1), + Register16::ES => self.es = self.es.wrapping_sub(1), + _ => {} + } + } + + pub fn set_reset_vector(&mut self, reset_vector: CpuAddress) { + self.reset_vector = reset_vector; + } + + pub fn get_reset_vector(&self) -> CpuAddress { + self.reset_vector + } + + pub fn reset_address(&mut self) { + if let CpuAddress::Segmented(segment, offset) = self.reset_vector { + self.cs = segment; + self.pc = offset; + } + } + + pub fn get_state(&self) -> CpuRegisterState { + CpuRegisterState { + ah: self.a.h(), + al: self.a.l(), + ax: self.a.x(), + bh: self.b.h(), + bl: self.b.l(), + bx: self.b.x(), + ch: self.c.h(), + cl: self.c.l(), + cx: self.c.x(), + dh: self.d.h(), + dl: self.d.l(), + dx: self.d.x(), + sp: self.sp, + bp: self.bp, + si: self.si, + di: self.di, + cs: self.cs, + ds: self.ds, + ss: self.ss, + es: self.es, + ip: self.ip(), + pc: self.pc, + flags: self.flags, + } + } + + /// Get a string representation of the CPU state. + /// This is used to display the CPU state viewer window in the debug GUI. + pub fn get_string_state(&self) -> CpuStringState { + CpuStringState { + ah: format!("{:02x}", self.a.h()), + al: format!("{:02x}", self.a.l()), + ax: format!("{:04x}", self.a.x()), + bh: format!("{:02x}", self.b.h()), + bl: format!("{:02x}", self.b.l()), + bx: format!("{:04x}", self.b.x()), + ch: format!("{:02x}", self.c.h()), + cl: format!("{:02x}", self.c.l()), + cx: format!("{:04x}", self.c.x()), + dh: format!("{:02x}", self.d.h()), + dl: format!("{:02x}", self.d.l()), + dx: format!("{:04x}", self.d.x()), + sp: format!("{:04x}", self.sp), + bp: format!("{:04x}", self.bp), + si: format!("{:04x}", self.si), + di: format!("{:04x}", self.di), + cs: format!("{:04x}", self.cs), + ds: format!("{:04x}", self.ds), + ss: format!("{:04x}", self.ss), + es: format!("{:04x}", self.es), + ip: format!("{:04x}", self.ip()), + pc: format!("{:04x}", self.pc), + c_fl: { + let fl = self.flags & CPU_FLAG_CARRY > 0; + format!("{:1}", fl as u8) + }, + p_fl: { + let fl = self.flags & CPU_FLAG_PARITY > 0; + format!("{:1}", fl as u8) + }, + a_fl: { + let fl = self.flags & CPU_FLAG_AUX_CARRY > 0; + format!("{:1}", fl as u8) + }, + z_fl: { + let fl = self.flags & CPU_FLAG_ZERO > 0; + format!("{:1}", fl as u8) + }, + s_fl: { + let fl = self.flags & CPU_FLAG_SIGN > 0; + format!("{:1}", fl as u8) + }, + t_fl: { + let fl = self.flags & CPU_FLAG_TRAP > 0; + format!("{:1}", fl as u8) + }, + i_fl: { + let fl = self.flags & CPU_FLAG_INT_ENABLE > 0; + format!("{:1}", fl as u8) + }, + d_fl: { + let fl = self.flags & CPU_FLAG_DIRECTION > 0; + format!("{:1}", fl as u8) + }, + o_fl: { + let fl = self.flags & CPU_FLAG_OVERFLOW > 0; + format!("{:1}", fl as u8) + }, + + piq: self.queue.to_string(), + flags: format!("{:04}", self.flags), + instruction_count: format!("{}", self.instruction_count), + cycle_count: format!("{}", self.cycle_num), + } + } + + /// Evaluate a string expression such as 'cs:ip' to an address. + /// Basic forms supported are [reg:reg], [reg:offset], [seg:offset] + pub fn eval_address(&self, expr: &str) -> Option { + lazy_static! { + static ref FLAT_REX: Regex = Regex::new(r"(?P[A-Fa-f\d]{5})$").unwrap(); + static ref SEGMENTED_REX: Regex = + Regex::new(r"(?P[A-Fa-f\d]{4}):(?P[A-Fa-f\d]{4})$").unwrap(); + static ref REGREG_REX: Regex = Regex::new(r"(?Pcs|ds|ss|es):(?P\w{2})$").unwrap(); + static ref REGOFFSET_REX: Regex = Regex::new(r"(?Pcs|ds|ss|es):(?P[A-Fa-f\d]{4})$").unwrap(); + } + + if FLAT_REX.is_match(expr) { + match u32::from_str_radix(expr, 16) { + Ok(address) => Some(CpuAddress::Flat(address)), + Err(_) => None, + } + } + else if let Some(caps) = SEGMENTED_REX.captures(expr) { + let segment_str = &caps["segment"]; + let offset_str = &caps["offset"]; + + let segment_u16r = u16::from_str_radix(segment_str, 16); + let offset_u16r = u16::from_str_radix(offset_str, 16); + + match (segment_u16r, offset_u16r) { + (Ok(segment), Ok(offset)) => Some(CpuAddress::Segmented(segment, offset)), + _ => None, + } + } + else if let Some(caps) = REGREG_REX.captures(expr) { + let reg1 = &caps["reg1"]; + let reg2 = &caps["reg2"]; + + let segment = match reg1 { + "cs" => self.cs, + "ds" => self.ds, + "ss" => self.ss, + "es" => self.es, + _ => 0, + }; + + let offset = match reg2 { + "ah" => self.a.h() as u16, + "al" => self.a.l() as u16, + "ax" => self.a.x(), + "bh" => self.b.h() as u16, + "bl" => self.b.l() as u16, + "bx" => self.b.x(), + "ch" => self.c.h() as u16, + "cl" => self.c.l() as u16, + "cx" => self.c.x(), + "dh" => self.d.h() as u16, + "dl" => self.d.l() as u16, + "dx" => self.d.x(), + "sp" => self.sp, + "bp" => self.bp, + "si" => self.si, + "di" => self.di, + "cs" => self.cs, + "ds" => self.ds, + "ss" => self.ss, + "es" => self.es, + "ip" => self.disassembly_ip(), // Use reentrant IP for disassembly + _ => 0, + }; + + Some(CpuAddress::Segmented(segment, offset)) + } + else if let Some(caps) = REGOFFSET_REX.captures(expr) { + let reg1 = &caps["reg1"]; + let offset_str = &caps["offset"]; + + let segment = match reg1 { + "cs" => self.cs, + "ds" => self.ds, + "ss" => self.ss, + "es" => self.es, + _ => 0, + }; + + let offset_u16r = u16::from_str_radix(offset_str, 16); + + match offset_u16r { + Ok(offset) => Some(CpuAddress::Segmented(segment, offset)), + _ => None, + } + } + else { + None + } + } + + /// Push an entry on to the call stack. This can either be a CALL or an INT. + pub fn push_call_stack(&mut self, entry: CallStackEntry, cs: u16, ip: u16) { + if self.call_stack.len() < CPU_CALL_STACK_LEN { + self.call_stack.push_back(entry); + + // Flag the specified CS:IP as a return address + let return_addr = NecVx0::calc_linear_address(cs, ip); + + self.bus.set_flags(return_addr as usize, MEM_RET_BIT); + } + else { + // TODO: set a flag to indicate that the call stack has overflowed? + } + } + + /// Rewind the call stack to the specified address. + /// + /// We have to rewind the call stack to the earliest appearance of this address we returned to, + /// because popping the call stack clears the return flag from the memory location, so we don't + /// support reentrancy. + /// + /// Maintaining a call stack is trickier than expected. JUMPs can RET, CALLS can JMP back, ISRs + /// may not always IRET, so there is no other reliable way to pop a "return" from CALL/INT other + /// than to mark the return address as the end of that CALL/INT and rewind when we reach that + /// address again. It isn't perfect, but "good enough" for debugging. + pub fn rewind_call_stack(&mut self, addr: u32) { + let mut return_addr: u32 = 0; + + let pos = self.call_stack.iter().position(|&call| { + return_addr = match call { + CallStackEntry::CallF { ret_cs, ret_ip, .. } => NecVx0::calc_linear_address(ret_cs, ret_ip), + CallStackEntry::Call { ret_cs, ret_ip, .. } => NecVx0::calc_linear_address(ret_cs, ret_ip), + CallStackEntry::Interrupt { ret_cs, ret_ip, .. } => NecVx0::calc_linear_address(ret_cs, ret_ip), + }; + + return_addr == addr + }); + + if let Some(found_idx) = pos { + let drained = self.call_stack.drain(found_idx..); + + drained.for_each(|drained_call| { + return_addr = match drained_call { + CallStackEntry::CallF { ret_cs, ret_ip, .. } => NecVx0::calc_linear_address(ret_cs, ret_ip), + CallStackEntry::Call { ret_cs, ret_ip, .. } => NecVx0::calc_linear_address(ret_cs, ret_ip), + CallStackEntry::Interrupt { ret_cs, ret_ip, .. } => NecVx0::calc_linear_address(ret_cs, ret_ip), + }; + + // Clear flags for returns we popped + self.bus.clear_flags(return_addr as usize, MEM_RET_BIT) + }) + } + else { + log::warn!("rewind_call_stack(): no matching return for [{:05X}]", addr); + } + } + + /// Resume from halted state + pub fn resume(&mut self) { + if self.halted { + //log::debug!("Resuming from halt"); + // It takes 6 or 7 cycles after INTR to enter INTA. + // 3 of these are resuming from suspend, so not accounted from here. + self.trace_comment("INTR"); + //log::debug!("resuming from halt with {} cycles", self.halt_resume_delay); + self.cycles(self.halt_resume_delay); + } + else { + log::warn!("resume() called but not halted!"); + } + self.halted = false; + } + + /// Set the status of the CPU's INTR line. + #[inline] + pub fn set_intr(&mut self, status: bool) { + self.intr = status; + } + + /// Set a terminating code address for the CPU. This is mostly used in conjunction with the + /// CPU validator or running standalone binaries. + pub fn set_end_address(&mut self, end: usize) { + #[cfg(feature = "cpu_validator")] + { + self.validator_end = end; + } + + self.end_addr = end; + } + + /// Removes any cycle states at address 0 + #[cfg(feature = "cpu_validator")] + fn clear_reset_cycle_states(&mut self) { + self.cycle_states.retain(|&x| x.addr != 0); + } + + /// Set CPU breakpoints from provided list. + /// + /// Clears bus breakpoint flags from previous breakpoint list before applying new. + pub fn set_breakpoints(&mut self, bp_list: Vec) { + // Clear bus flags for current breakpoints + self.breakpoints.iter().for_each(|bp| match bp { + BreakPointType::ExecuteFlat(addr) => { + log::debug!("Clearing breakpoint on execute at address: {:05X}", *addr); + self.bus.clear_flags(*addr as usize, MEM_BPE_BIT); + } + BreakPointType::MemAccessFlat(addr) => { + self.bus.clear_flags(*addr as usize, MEM_BPA_BIT); + } + BreakPointType::Interrupt(vector) => { + self.int_flags[*vector as usize] = 0; + } + BreakPointType::StartWatch(addr) => { + self.bus.clear_flags(*addr as usize, MEM_SW_BIT); + } + BreakPointType::StopWatch(addr) => { + self.bus.clear_flags(*addr as usize, MEM_SW_BIT); + } + _ => {} + }); + + // Replace current breakpoint list + self.breakpoints = bp_list; + + // Set bus flags for new breakpoints + self.breakpoints.iter().for_each(|bp| match bp { + BreakPointType::ExecuteFlat(addr) => { + log::debug!("Setting breakpoint on execute at address: {:05X}", *addr); + self.bus.set_flags(*addr as usize, MEM_BPE_BIT); + } + BreakPointType::MemAccessFlat(addr) => { + log::debug!("Setting breakpoint on memory access at address: {:05X}", *addr); + self.bus.set_flags(*addr as usize, MEM_BPA_BIT); + } + BreakPointType::Interrupt(vector) => { + self.int_flags[*vector as usize] = INTERRUPT_BREAKPOINT; + } + BreakPointType::StartWatch(addr) => { + self.bus.set_flags(*addr as usize, MEM_SW_BIT); + } + BreakPointType::StopWatch(addr) => { + self.bus.set_flags(*addr as usize, MEM_SW_BIT); + } + _ => {} + }); + } + + pub fn add_breakpoint(&mut self, bp: &BreakPointType) { + match bp { + BreakPointType::ExecuteFlat(addr) => { + log::debug!("Clearing breakpoint on execute at address: {:05X}", *addr); + self.bus.set_flags(*addr as usize, MEM_BPE_BIT); + } + BreakPointType::MemAccessFlat(addr) => { + self.bus.set_flags(*addr as usize, MEM_BPA_BIT); + } + BreakPointType::Interrupt(vector) => { + self.int_flags[*vector as usize] = INTERRUPT_BREAKPOINT; + } + BreakPointType::StartWatch(addr) => { + self.bus.set_flags(*addr as usize, MEM_SW_BIT); + } + BreakPointType::StopWatch(addr) => { + self.bus.set_flags(*addr as usize, MEM_SW_BIT); + } + _ => { + // Unsupported breakpoint type - ignore for now + return; + } + } + } + + pub fn set_stopwatch(&mut self, sw_idx: usize, start: u32, stop: u32) { + if self.stopwatches.is_empty() { + self.stopwatches.push(None); + } + self.stopwatches[sw_idx] = Some(CycleStopWatch::new(start, stop)); + } + + pub fn start_stopwatch(&mut self, sw_idx: usize) { + if let Some(sw) = &mut self.stopwatches[sw_idx] { + sw.start(); + } + } + + pub fn stop_stopwatch(&mut self, sw_idx: usize) { + if let Some(sw) = &mut self.stopwatches[sw_idx] { + sw.stop(); + } + } + + fn update_sw_flag(&mut self) { + self.stopwatch_running = false; + for sw in self.stopwatches.iter().flatten() { + if sw.running() { + self.stopwatch_running = true; + } + } + } + + pub fn get_sw_data(&self) -> Vec { + self.stopwatches.iter().flatten().map(|sw| sw.get_data()).collect() + } + + pub fn set_step_over_breakpoint(&mut self, target: CpuAddress) { + self.step_over_breakpoint = Some(u32::from(target)); + } + + pub fn get_step_over_breakpoint(&self) -> Option { + match self.step_over_breakpoint { + Some(addr) => Some(CpuAddress::Flat(addr)), + None => None, + } + } + + pub fn get_breakpoint_flag(&self) -> bool { + matches!(self.state, CpuState::BreakpointHit) + } + + pub fn set_breakpoint_flag(&mut self) { + self.state = CpuState::BreakpointHit; + } + + pub fn clear_breakpoint_flag(&mut self) { + self.state = CpuState::Normal; + } + + pub fn dump_instruction_history_string(&self) -> String { + let mut disassembly_string = String::new(); + + for i in &self.instruction_history { + match i { + HistoryEntry::InstructionEntry { + cs, + ip, + cycles: _, + interrupt, + jump: _, + i, + } => { + let i_string = format!( + "{:05X}{} [{:04X}:{:04X}] {}\n", + i.address, + if *interrupt { '*' } else { ' ' }, + *cs, + *ip, + i + ); + disassembly_string.push_str(&i_string); + } + _ => {} + } + } + disassembly_string + } + + pub fn dump_instruction_history_tokens(&self) -> Vec> { + let mut history_vec = Vec::new(); + + for i in &self.instruction_history { + let mut i_token_vec = Vec::new(); + match i { + HistoryEntry::InstructionEntry { + cs, + ip, + cycles, + interrupt, + jump, + i, + } => { + if *jump { + i_token_vec.push(SyntaxToken::Formatter(SyntaxFormatType::HighlightLine( + HighlightType::Info, + ))); + } + i_token_vec.push(SyntaxToken::MemoryAddressFlat(i.address, format!("{:05X}", i.address))); + i_token_vec.push(SyntaxToken::MemoryAddressSeg16( + *cs, + *ip, + format!("{:04X}:{:04X}{}", cs, ip, if *interrupt { '*' } else { ' ' }), + )); + i_token_vec.push(SyntaxToken::InstructionBytes(format!("{:012}", " "))); + i_token_vec.extend(i.tokenize()); + i_token_vec.push(SyntaxToken::Formatter(SyntaxFormatType::Tab)); + i_token_vec.push(SyntaxToken::Text(format!("{}", *cycles))); + } + HistoryEntry::InterruptEntry { cs, ip, cycles: _, iv } => { + i_token_vec.push(SyntaxToken::Formatter(SyntaxFormatType::HighlightLine( + HighlightType::Alert, + ))); + i_token_vec.push(SyntaxToken::MemoryAddressFlat(0, format!("{:05}", ""))); + i_token_vec.push(SyntaxToken::MemoryAddressSeg16(*cs, *ip, String::from(" "))); + i_token_vec.push(SyntaxToken::InstructionBytes(format!("{:012}", ""))); + i_token_vec.push(SyntaxToken::Text(format!("INT {:02X}", iv))); + } + HistoryEntry::NmiEntry { cs, ip } => { + i_token_vec.push(SyntaxToken::Formatter(SyntaxFormatType::HighlightLine( + HighlightType::Alert, + ))); + i_token_vec.push(SyntaxToken::MemoryAddressFlat(0, format!("{:05}", ""))); + i_token_vec.push(SyntaxToken::MemoryAddressSeg16(*cs, *ip, String::from(" "))); + i_token_vec.push(SyntaxToken::InstructionBytes(format!("{:012}", ""))); + i_token_vec.push(SyntaxToken::Text(String::from("NMI"))); + } + HistoryEntry::TrapEntry { cs, ip } => { + i_token_vec.push(SyntaxToken::Formatter(SyntaxFormatType::HighlightLine( + HighlightType::Alert, + ))); + i_token_vec.push(SyntaxToken::MemoryAddressFlat(0, format!("{:05}", ""))); + i_token_vec.push(SyntaxToken::MemoryAddressSeg16(*cs, *ip, String::from(" "))); + i_token_vec.push(SyntaxToken::InstructionBytes(format!("{:012}", ""))); + i_token_vec.push(SyntaxToken::Text(String::from("TRAP"))); + } + } + history_vec.push(i_token_vec); + } + history_vec + } + + pub fn dump_call_stack(&self) -> String { + let mut call_stack_string = String::new(); + + for call in &self.call_stack { + match call { + CallStackEntry::Call { + ret_cs, + ret_ip, + call_ip, + } => { + call_stack_string.push_str(&format!("{:04X}:{:04X} CALL {:04X}\n", ret_cs, ret_ip, call_ip)); + } + CallStackEntry::CallF { + ret_cs, + ret_ip, + call_cs, + call_ip, + } => { + call_stack_string.push_str(&format!( + "{:04X}:{:04X} CALL FAR {:04X}:{:04X}\n", + ret_cs, ret_ip, call_cs, call_ip + )); + } + CallStackEntry::Interrupt { + ret_cs, + ret_ip, + call_cs, + call_ip, + itype, + number, + ah, + } => { + call_stack_string.push_str(&format!( + "{:04X}:{:04X} INT {:02X}h {:04X}:{:04X} type={:?} AH=={:02X}\n", + ret_cs, ret_ip, number, call_cs, call_ip, itype, ah + )); + } + } + } + + call_stack_string + } + + #[inline] + pub fn trace_print(&mut self, trace_str: &str) { + if self.trace_logger.is_some() { + self.trace_logger.println(trace_str); + } + } + + #[inline] + pub fn trace_emit(&mut self, trace_str: &str) { + if self.trace_logger.is_some() { + self.trace_logger.println(trace_str); + } + } + + pub fn trace_flush(&mut self) { + if self.trace_logger.is_some() { + self.trace_logger.flush(); + } + + #[cfg(feature = "cpu_validator")] + { + if let Some(val) = &mut self.validator { + val.flush(); + } + } + } + + #[inline] + pub fn trace_comment(&mut self, comment: &'static str) { + if self.trace_enabled && (self.trace_mode == TraceMode::CycleText) { + self.trace_comment.push(comment); + } + } + + #[inline] + pub fn trace_instr(&mut self, instr: u16) { + self.trace_instr = instr; + } + + pub fn dump_cs(&self, path: &Path) { + let filename = path.to_path_buf(); + + let len = 0x10000; + let address = (self.cs as usize) << 4; + log::debug!("Dumping {} bytes at address {:05X}", len, address); + let cs_slice = self.bus.get_slice_at(address, len); + + match std::fs::write(filename.clone(), &cs_slice) { + Ok(_) => { + log::debug!("Wrote memory dump: {}", filename.display()) + } + Err(e) => { + log::error!("Failed to write memory dump '{}': {}", filename.display(), e) + } + } + } + + pub fn get_service_event(&mut self) -> Option { + self.service_events.pop_front() + } + + pub fn get_cycle_trace(&self) -> &Vec { + &self.trace_str_vec + } + pub fn get_cycle_trace_tokens(&self) -> &Vec> { + &self.trace_token_vec + } + pub fn get_cycle_ct(&self) -> (u64, u64) { + (self.cycle_num, self.halt_cycles) + } + + #[cfg(feature = "cpu_validator")] + pub fn get_validator_state(&self) -> CpuValidatorState { + self.validator_state + } + + #[cfg(feature = "cpu_validator")] + pub fn get_validator(&self) -> &Option> { + &self.validator + } + + #[cfg(feature = "cpu_validator")] + pub fn get_validator_mut(&mut self) -> &mut Option> { + &mut self.validator + } +} diff --git a/core/src/cpu_vx0/modrm.rs b/core/src/cpu_vx0/modrm.rs new file mode 100644 index 00000000..b87a709a --- /dev/null +++ b/core/src/cpu_vx0/modrm.rs @@ -0,0 +1,442 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::modrm.rs + + Routines to handle loading and parsing of modrm bytes. + +*/ +use crate::{ + bytequeue::*, + cpu_common::{AddressingMode, Displacement}, + cpu_vx0::*, +}; + +pub const MODRM_REG_MASK: u8 = 0b00_111_000; +pub const MODRM_ADDR_MASK: u8 = 0b11_000_111; +//pub const MODRM_MOD_MASK: u8 = 0b11_000_000; + +const MODRM_ADDR_BX_SI: u8 = 0b00_000_000; +const MODRM_ADDR_BX_DI: u8 = 0b00_000_001; +const MODRM_ADDR_BP_SI: u8 = 0b00_000_010; +const MODRM_ADDR_BP_DI: u8 = 0b00_000_011; +const MODRM_ADDR_SI: u8 = 0b00_000_100; +const MODRM_ADDR_DI: u8 = 0b00_000_101; +const MODRM_ADDR_DISP16: u8 = 0b00_000_110; +const MODRM_ADDR_BX: u8 = 0b00_000_111; + +const MODRM_ADDR_BX_SI_DISP8: u8 = 0b01_000_000; +const MODRM_ADDR_BX_DI_DISP8: u8 = 0b01_000_001; +const MODRM_ADDR_BP_SI_DISP8: u8 = 0b01_000_010; +const MODRM_ADDR_BP_DI_DISP8: u8 = 0b01_000_011; +const MODRM_ADDR_SI_DISP8: u8 = 0b01_000_100; +const MODRM_ADDR_DI_DISP8: u8 = 0b01_000_101; +const MODRM_ADDR_BP_DISP8: u8 = 0b01_000_110; +const MODRM_ADDR_BX_DISP8: u8 = 0b01_000_111; + +const MODRM_ADDR_BX_SI_DISP16: u8 = 0b10_000_000; +const MODRM_ADDR_BX_DI_DISP16: u8 = 0b10_000_001; +const MODRM_ADDR_BP_SI_DISP16: u8 = 0b10_000_010; +const MODRM_ADDR_BP_DI_DISP16: u8 = 0b10_000_011; +const MODRM_ADDR_SI_DISP16: u8 = 0b10_000_100; +const MODRM_ADDR_DI_DISP16: u8 = 0b10_000_101; +const MODRM_ADDR_BP_DISP16: u8 = 0b10_000_110; +const MODRM_ADDR_BX_DISP16: u8 = 0b10_000_111; + +/* +const MODRM_REG_AX_OR_AL: u8 = 0b00_000_000; +const MODRM_REG_CX_OR_CL: u8 = 0b00_000_001; +const MODRM_REG_DX_OR_DL: u8 = 0b00_000_010; +const MODRM_REG_BX_OR_BL: u8 = 0b00_000_011; +const MODRM_REG_SP_OR_AH: u8 = 0b00_000_100; +const MODRM_REG_BP_OR_CH: u8 = 0b00_000_101; +const MODRM_REG_SI_OR_DH: u8 = 0b00_000_110; +const MODRM_RED_DI_OR_BH: u8 = 0b00_000_111; +*/ + +#[derive(Copy, Clone)] +pub struct ModRmByte { + _byte: u8, + b_mod: u8, + b_reg: u8, + b_rm: u8, + pre_disp_cost: u8, + post_disp_cost: u8, + disp_mc: u16, + disp: Displacement, + addressing_mode: AddressingMode, +} + +impl Default for ModRmByte { + fn default() -> Self { + Self { + _byte: 0, + b_mod: 0, + b_reg: 0, + b_rm: 0, + pre_disp_cost: 0, + post_disp_cost: 0, + disp_mc: 0, + disp: Displacement::NoDisp, + addressing_mode: AddressingMode::BxSi, + } + } +} + +// Microcode addresses for EA procedures, pre-displacement +const EA_INSTR_TABLE_PRE: [[u16; 5]; 24] = [ + [0x1d4, 0x1d5, 0x1d6, MC_JUMP, MC_NONE], // MODRM_ADDR_BX_SI + [0x1da, MC_JUMP, 0x1d8, 0x1d9, MC_JUMP], // MODRM_ADDR_BX_DI + [0x1db, MC_JUMP, 0x1d5, 0x1d6, MC_JUMP], // MODRM_ADDR_BP_SI + [0x1d7, 0x1d8, 0x1d9, MC_JUMP, MC_NONE], // MODRM_ADDR_BP_DI + [0x003, MC_JUMP, MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_SI + [0x01f, MC_JUMP, MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_DI + [MC_NONE, MC_NONE, MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_DISP16 + [0x037, MC_JUMP, MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_BX + [0x1d4, 0x1d5, 0x1d6, MC_JUMP, MC_NONE], // MODRM_ADDR_BX_SI_DISP8 + [0x1da, MC_JUMP, 0x1d8, 0x1d9, MC_JUMP], // MODRM_ADDR_BX_DI_DISP8 + [0x1db, MC_JUMP, 0x1d5, 0x1d6, MC_JUMP], // MODRM_ADDR_BP_SI_DISP8 + [0x1d7, 0x1d8, 0x1d9, MC_JUMP, MC_NONE], // MODRM_ADDR_BP_DI_DISP8 + [0x003, MC_JUMP, MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_SI_DISP8 + [0x01f, MC_JUMP, MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_DI_DISP8 + [0x023, MC_JUMP, MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_BP_DISP8 + [0x037, MC_JUMP, MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_BX_DISP8 + [0x1d4, 0x1d5, 0x1d6, MC_JUMP, MC_NONE], // MODRM_ADDR_BX_SI_DISP16 + [0x1da, MC_JUMP, 0x1d8, 0x1d9, MC_JUMP], // MODRM_ADDR_BX_DI_DISP16 + [0x1db, MC_JUMP, 0x1d5, 0x1d6, MC_JUMP], // MODRM_ADDR_BP_SI_DISP16 + [0x1d7, 0x1d8, 0x1d9, MC_JUMP, MC_NONE], // MODRM_ADDR_BP_DI_DISP16 + [0x003, MC_JUMP, MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_SI_DISP16 + [0x01f, MC_JUMP, MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_DI_DISP16 + [0x023, MC_JUMP, MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_BP_DISP16 + [0x037, MC_JUMP, MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_BX_DISP16 +]; + +// Microcode addresses for EA procedures, post-displacement, EA loaded +const EA_INSTR_TABLE_POST: [[u16; 3]; 24] = [ + [MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_BX_SI + [MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_BX_DI + [MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_BP_SI + [MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_BP_DI + [MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_SI + [MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_DI + [MC_JUMP, MC_NONE, MC_NONE], // MODRM_ADDR_DISP16 + [MC_NONE, MC_NONE, MC_NONE], // MODRM_ADDR_BX + [MC_JUMP, 0x1e0, MC_JUMP], // MODRM_ADDR_BX_SI_DISP8 + [MC_JUMP, 0x1e0, MC_JUMP], // MODRM_ADDR_BX_DI_DISP8 + [MC_JUMP, 0x1e0, MC_JUMP], // MODRM_ADDR_BP_SI_DISP8 + [MC_JUMP, 0x1e0, MC_JUMP], // MODRM_ADDR_BP_DI_DISP8 + [MC_JUMP, 0x1e0, MC_JUMP], // MODRM_ADDR_DI_DISP8 + [MC_JUMP, 0x1e0, MC_JUMP], // MODRM_ADDR_SI_DISP8 + [MC_JUMP, 0x1e0, MC_JUMP], // MODRM_ADDR_BP_DISP8 + [MC_JUMP, 0x1e0, MC_JUMP], // MODRM_ADDR_BX_DISP8 + [0x1e0, MC_JUMP, MC_NONE], // MODRM_ADDR_BX_SI_DISP16 + [0x1e0, MC_JUMP, MC_NONE], // MODRM_ADDR_BP_DI_DISP16 + [0x1e0, MC_JUMP, MC_NONE], // MODRM_ADDR_BX_SI_DISP16 + [0x1e0, MC_JUMP, MC_NONE], // MODRM_ADDR_BP_DI_DISP16 + [0x1e0, MC_JUMP, MC_NONE], // MODRM_ADDR_SI_DISP16 + [0x1e0, MC_JUMP, MC_NONE], // MODRM_ADDR_DI_DISP16 + [0x1e0, MC_JUMP, MC_NONE], // MODRM_ADDR_BP_DISP16 + [0x1e0, MC_JUMP, MC_NONE], // MODRM_ADDR_BX_DISP16 +]; + +const MODRM_TABLE: [ModRmByte; 256] = { + let mut table: [ModRmByte; 256] = [ModRmByte { + _byte: 0, + b_mod: 0, + b_reg: 0, + b_rm: 0, + pre_disp_cost: 0, + post_disp_cost: 0, + disp_mc: 0, + disp: Displacement::NoDisp, + addressing_mode: AddressingMode::BxSi, + }; 256]; + let mut byte = 0; + + loop { + let mut displacement = Displacement::NoDisp; + + let b_mod = (byte >> 6) & 0x03; + + match b_mod { + 0b00 => { + // Addressing mode [disp16] is a single mode of 0b00 + if byte & MODRM_ADDR_MASK == MODRM_ADDR_DISP16 { + displacement = Displacement::Pending16; + } + } + 0b01 => { + // 0b01 signifies an 8 bit displacement (sign-extended to 16) + displacement = Displacement::Pending8; + } + 0b10 => { + // 0b10 signifies a 16 bit displacement + displacement = Displacement::Pending16; + } + _ => displacement = Displacement::NoDisp, + } + + // Set the EA calculation costs for each addressing mode. + // We divide these into two values, representing microcode instructions before and after + // loading the displacement. Time spent loading the displacement itself is dependent on the + // state of the prefetch queue, so can't be known ahead of time. + // + // Oddly, fetching an 8-bit displacement takes longer than 16-bit! + // This is due to an extra jump at microcode line 1de. + let (pre_disp_cost, post_disp_cost, disp_mc) = match byte & MODRM_ADDR_MASK { + MODRM_ADDR_BX_SI => (4, 0, 0), + MODRM_ADDR_BX_DI => (5, 0, 0), + MODRM_ADDR_BP_SI => (5, 0, 0), + MODRM_ADDR_BP_DI => (4, 0, 0), + MODRM_ADDR_SI => (2, 0, 0), + MODRM_ADDR_DI => (2, 0, 0), + MODRM_ADDR_DISP16 => (0, 1, 0x1DC), + MODRM_ADDR_BX => (2, 0, 0), + MODRM_ADDR_BX_SI_DISP8 => (4, 3, 0x1DE), + MODRM_ADDR_BX_DI_DISP8 => (5, 3, 0x1DE), + MODRM_ADDR_BP_SI_DISP8 => (5, 3, 0x1DE), + MODRM_ADDR_BP_DI_DISP8 => (4, 3, 0x1DE), + MODRM_ADDR_SI_DISP8 => (2, 3, 0x1DE), + MODRM_ADDR_DI_DISP8 => (2, 3, 0x1DE), + MODRM_ADDR_BP_DISP8 => (2, 3, 0x1DE), + MODRM_ADDR_BX_DISP8 => (2, 3, 0x1DE), + MODRM_ADDR_BX_SI_DISP16 => (4, 2, 0x1DE), + MODRM_ADDR_BX_DI_DISP16 => (5, 2, 0x1DE), + MODRM_ADDR_BP_SI_DISP16 => (5, 2, 0x1DE), + MODRM_ADDR_BP_DI_DISP16 => (4, 2, 0x1DE), + MODRM_ADDR_SI_DISP16 => (2, 2, 0x1DE), + MODRM_ADDR_DI_DISP16 => (2, 2, 0x1DE), + MODRM_ADDR_BP_DISP16 => (2, 2, 0x1DE), + MODRM_ADDR_BX_DISP16 => (2, 2, 0x1DE), + _ => (0, 0, 0), + }; + + // Set the addressing mode based on the cominbation of Mod and R/M bitfields + Displacement. + let (addressing_mode, displacement) = match byte & MODRM_ADDR_MASK { + MODRM_ADDR_BX_SI => (AddressingMode::BxSi, Displacement::NoDisp), + MODRM_ADDR_BX_DI => (AddressingMode::BxDi, Displacement::NoDisp), + MODRM_ADDR_BP_SI => (AddressingMode::BpSi, Displacement::NoDisp), + MODRM_ADDR_BP_DI => (AddressingMode::BpDi, Displacement::NoDisp), + MODRM_ADDR_SI => (AddressingMode::Si, Displacement::NoDisp), + MODRM_ADDR_DI => (AddressingMode::Di, Displacement::NoDisp), + MODRM_ADDR_DISP16 => (AddressingMode::Disp16(displacement), displacement), + MODRM_ADDR_BX => (AddressingMode::Bx, Displacement::NoDisp), + MODRM_ADDR_BX_SI_DISP8 => (AddressingMode::BxSiDisp8(displacement), displacement), + MODRM_ADDR_BX_DI_DISP8 => (AddressingMode::BxDiDisp8(displacement), displacement), + MODRM_ADDR_BP_SI_DISP8 => (AddressingMode::BpSiDisp8(displacement), displacement), + MODRM_ADDR_BP_DI_DISP8 => (AddressingMode::BpDiDisp8(displacement), displacement), + MODRM_ADDR_SI_DISP8 => (AddressingMode::SiDisp8(displacement), displacement), + MODRM_ADDR_DI_DISP8 => (AddressingMode::DiDisp8(displacement), displacement), + MODRM_ADDR_BP_DISP8 => (AddressingMode::BpDisp8(displacement), displacement), + MODRM_ADDR_BX_DISP8 => (AddressingMode::BxDisp8(displacement), displacement), + MODRM_ADDR_BX_SI_DISP16 => (AddressingMode::BxSiDisp16(displacement), displacement), + MODRM_ADDR_BX_DI_DISP16 => (AddressingMode::BxDiDisp16(displacement), displacement), + MODRM_ADDR_BP_SI_DISP16 => (AddressingMode::BpSiDisp16(displacement), displacement), + MODRM_ADDR_BP_DI_DISP16 => (AddressingMode::BpDiDisp16(displacement), displacement), + MODRM_ADDR_SI_DISP16 => (AddressingMode::SiDisp16(displacement), displacement), + MODRM_ADDR_DI_DISP16 => (AddressingMode::DiDisp16(displacement), displacement), + MODRM_ADDR_BP_DISP16 => (AddressingMode::BpDisp16(displacement), displacement), + MODRM_ADDR_BX_DISP16 => (AddressingMode::BxDisp16(displacement), displacement), + _ => (AddressingMode::RegisterMode, Displacement::NoDisp), + }; + + // 'REG' field specifies either register operand or opcode extension. There's no way + // to know without knowing the opcode, which we don't + let b_reg: u8 = (byte >> 3) & 0x07; + + // 'R/M' field is last three bits + let b_rm: u8 = byte & 0x07; + + table[byte as usize] = ModRmByte { + _byte: byte, + b_mod, + b_reg, + b_rm, + pre_disp_cost, + post_disp_cost, + disp_mc, + disp: displacement, + addressing_mode, + }; + + if byte < 255 { + byte += 1; + } + else { + break; + } + } + + table +}; + +impl ModRmByte { + /// Read the modrm byte and look up the appropriate value from the modrm table. + /// Load any displacement, then return modrm struct and size of modrm + displacement. + pub fn read(bytes: &mut impl ByteQueue) -> (ModRmByte, u32) { + let byte = bytes.q_read_u8(QueueType::Subsequent, QueueReader::Biu); + let mut modrm = MODRM_TABLE[byte as usize]; + let mut disp_size = 0; + + // If modrm is an addressing mode, spend cycles in EA calculation + if modrm.b_mod != 0b11 { + bytes.wait_i(1, &[MC_JUMP]); + bytes.wait_i( + modrm.pre_disp_cost as u32, + &EA_INSTR_TABLE_PRE[(modrm.b_mod << 3 | modrm.b_rm) as usize], + ); + + // Load any displacement + disp_size = ModRmByte::load_displacement(&mut modrm, bytes); + + bytes.wait_i( + modrm.post_disp_cost as u32, + &EA_INSTR_TABLE_POST[(modrm.b_mod << 3 | modrm.b_rm) as usize], + ); + } + + (modrm, disp_size + 1) + } + + /// Load any displacement the modrm might have. The modrm table only has 'pending' displacement values, + /// which must be resolved to actual displacement values. + pub fn load_displacement(&mut self, bytes: &mut impl ByteQueue) -> u32 { + let (displacement, size) = match self.disp { + Displacement::Pending8 => { + bytes.set_pc(self.disp_mc); + let tdisp = bytes.q_read_i8(QueueType::Subsequent, QueueReader::Biu); + (Displacement::Disp8(tdisp), 1) + } + Displacement::Pending16 => { + bytes.set_pc(self.disp_mc); + let tdisp = bytes.q_read_i16(QueueType::Subsequent, QueueReader::Biu); + (Displacement::Disp16(tdisp), 2) + } + _ => (Displacement::NoDisp, 0), + }; + + match &mut self.addressing_mode { + AddressingMode::Disp16(d) => *d = displacement, + AddressingMode::BxSiDisp8(d) => *d = displacement, + AddressingMode::BxDiDisp8(d) => *d = displacement, + AddressingMode::BpSiDisp8(d) => *d = displacement, + AddressingMode::BpDiDisp8(d) => *d = displacement, + AddressingMode::SiDisp8(d) => *d = displacement, + AddressingMode::DiDisp8(d) => *d = displacement, + AddressingMode::BpDisp8(d) => *d = displacement, + AddressingMode::BxDisp8(d) => *d = displacement, + AddressingMode::BxSiDisp16(d) => *d = displacement, + AddressingMode::BxDiDisp16(d) => *d = displacement, + AddressingMode::BpSiDisp16(d) => *d = displacement, + AddressingMode::BpDiDisp16(d) => *d = displacement, + AddressingMode::SiDisp16(d) => *d = displacement, + AddressingMode::DiDisp16(d) => *d = displacement, + AddressingMode::BpDisp16(d) => *d = displacement, + AddressingMode::BxDisp16(d) => *d = displacement, + _ => {} + } + + size + } + + // Interpret the 'R/M' field as an 8 bit register selector + pub fn get_op1_reg8(&self) -> Register8 { + match self.b_rm { + 0x00 => Register8::AL, + 0x01 => Register8::CL, + 0x02 => Register8::DL, + 0x03 => Register8::BL, + 0x04 => Register8::AH, + 0x05 => Register8::CH, + 0x06 => Register8::DH, + 0x07 => Register8::BH, + _ => unreachable!("impossible Register8"), + } + } + // Interpret the 'R/M' field as a 16 bit register selector + pub fn get_op1_reg16(&self) -> Register16 { + match self.b_rm { + 0x00 => Register16::AX, + 0x01 => Register16::CX, + 0x02 => Register16::DX, + 0x03 => Register16::BX, + 0x04 => Register16::SP, + 0x05 => Register16::BP, + 0x06 => Register16::SI, + 0x07 => Register16::DI, + _ => unreachable!("impossible Register16"), + } + } + // Interpret the 'REG' field as an 8 bit register selector + pub fn get_op2_reg8(&self) -> Register8 { + match self.b_reg { + 0x00 => Register8::AL, + 0x01 => Register8::CL, + 0x02 => Register8::DL, + 0x03 => Register8::BL, + 0x04 => Register8::AH, + 0x05 => Register8::CH, + 0x06 => Register8::DH, + 0x07 => Register8::BH, + _ => unreachable!("impossible Register8"), + } + } + // Interpret the 'REG' field as a 16 bit register selector + pub fn get_op2_reg16(&self) -> Register16 { + match self.b_reg { + 0x00 => Register16::AX, + 0x01 => Register16::CX, + 0x02 => Register16::DX, + 0x03 => Register16::BX, + 0x04 => Register16::SP, + 0x05 => Register16::BP, + 0x06 => Register16::SI, + 0x07 => Register16::DI, + _ => unreachable!("impossible Register16"), + } + } + // Intepret the 'REG' field as a 16 bit segment register selector + pub fn get_op2_segmentreg16(&self) -> Register16 { + match self.b_reg { + 0x00 => Register16::ES, + 0x01 => Register16::CS, + 0x02 => Register16::SS, + 0x03 => Register16::DS, + 0x04 => Register16::ES, + 0x05 => Register16::CS, + 0x06 => Register16::SS, + 0x07 => Register16::DS, + _ => Register16::InvalidRegister, + } + } + // Intepret the 'REG' field as a 3 bit opcode extension + pub fn get_op_extension(&self) -> u8 { + self.b_reg + } + pub fn get_addressing_mode(&self) -> AddressingMode { + self.addressing_mode + } +} diff --git a/core/src/cpu_vx0/muldiv.rs b/core/src/cpu_vx0/muldiv.rs new file mode 100644 index 00000000..0ead22d1 --- /dev/null +++ b/core/src/cpu_vx0/muldiv.rs @@ -0,0 +1,829 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::muldiv.rs + + This module implements the microcode algorithm for multiplication and + division on the 8088 for accurate cycle timings. + +*/ + +use crate::{cpu_common::alu::*, cpu_vx0::*}; +pub trait Cord: Sized { + fn cord(self, cpu: &mut NecVx0, a: u16, b: u16, c: u16) -> Result<(u16, u16, bool), bool>; +} + +macro_rules! impl_cord { + ($prim:ty) => { + impl Cord for $prim { + /// Implementation of the 8088 microcode CORD division co-routine. + /// Implemented for either 8 bit or 16 bit operand. + fn cord(self, cpu: &mut NecVx0, a: u16, b: u16, c: u16) -> Result<(u16, u16, bool), bool> { + let mut internal_counter; + + let mut tmpa: u16 = a; + let tmpb: u16 = b; + let mut tmpc: u16 = c; + let mut sigma: u16; + let mut sigma_s: Self; + + let mut carry; + let mut carry_sub; + + // 188: | SUBT tmpa + (_, carry, _, _) = (tmpa as Self).alu_sub(tmpb as Self); + // 189: SIGMA->. | MAXC + internal_counter = Self::BITS; + + cpu.cycles_i(3, &[0x188, 0x189, 0x18a]); + + // 18a: | NCY INT0 + if !carry { + // Jump delay to INT0 procedure + cpu.cycle_i(MC_JUMP); + //log::debug!("cord: div overflow"); + return Err(false); + } + + // The main CORD loop is between 18b and 196. + while internal_counter > 0 { + //println!("ic: {} tmpa: {} tmpb: {} tmpc: {}", internal_counter, tmpa, tmpb, tmpc); + // 18b: + (sigma_s, carry) = (tmpc as Self).alu_rcl(1, carry); + tmpc = sigma_s as u16; + + // 18c: + (sigma_s, carry) = (tmpa as Self).alu_rcl(1, carry); + + // 18d: + tmpa = sigma_s as u16; + (sigma_s, carry_sub, _, _) = (tmpa as Self).alu_sub(tmpb as Self); + sigma = sigma_s as u16; + + cpu.cycles_i(4, &[0x18b, 0x18c, 0x18d, 0x18e]); + + // 18e: + if carry { + // Jump delay + cpu.cycles_i(3, &[MC_JUMP, 0x195, 0x196]); + // 195: + carry = false; + // 196: SIGMA->tmpa | NCZ 3 + tmpa = sigma; + internal_counter -= 1; + if internal_counter > 0 { + // 196: SIGMA->tmpa | NCZ 3 + cpu.cycle_i(MC_JUMP); + //println!(" cord(): in CORD: tmpa: {:04x} tmpc: {:04x}", tmpa, tmpc); + continue; + } + else { + // Continue to 197: + cpu.cycles_i(2, &[0x197, MC_JUMP]); + } + } + else { + // 18f: SIGMA->. | F + carry = carry_sub; + // SET FLAGS HERE + + cpu.cycles_i(2, &[0x18f, 0x190]); + + // 190: NCY 14 + if !carry { + // JMP delay + + // 196: SIGMA->tmpa | NCZ 3 + tmpa = sigma; + cpu.cycles_i(2, &[MC_JUMP, 0x196]); + internal_counter -= 1; + if internal_counter > 0 { + // 196: SIGMA->tmpa | NCZ 3 + cpu.cycle_i(MC_JUMP); + //println!(" cord(): in CORD: tmpa: {:04x} tmpc: {:04x}", tmpa, tmpc); + continue; // JMP to 3 + } + else { + // Continue to 197: + cpu.cycles_i(2, &[0x197, MC_JUMP]); + } + } + else { + cpu.cycle_i(0x191); + // 191: | NCZ 3 + internal_counter -= 1; + if internal_counter > 0 { + cpu.cycle_i(MC_JUMP); + //println!(" cord(): in CORD: tmpa: {:04x} tmpc: {:04x}", tmpa, tmpc); + continue; // JMP to 3 + } + else { + // Continue to 192: + } + } + } + //println!(" cord(): in CORD: tmpa: {:04x} tmpc: {:04x}", tmpa, tmpc); + } + + // 192 + (sigma_s, carry) = (tmpc as Self).alu_rcl(1, carry); + + // 193: SIGMA->tmpc + tmpc = sigma_s as u16; + + // 194: SIGMA->no dest | RTN + (_, carry) = (tmpc as Self).alu_rcl(1, carry); + + cpu.cycles_i(4, &[0x192, 0x193, 0x194, MC_RTN]); + //println!("cord_finish(): tmpc: {} tmpa: {}", tmpc, tmpa); + + Ok((tmpc, tmpa, carry)) + } + } + }; +} + +impl_cord!(u8); +impl_cord!(u16); + +pub trait Corx: Sized { + fn corx(self, cpu: &mut NecVx0, b: u16, c: u16, carry: bool) -> (u16, u16); +} + +macro_rules! impl_corx { + ($prim:ty) => { + impl Corx for $prim { + /// Implementation of the 8088 microcode CORX multiplication co-routine. + /// Implemented for either 8 bit or 16 bit operands. + /// tmpa is used to select the size of the operation, but the value is not used. + fn corx(self, cpu: &mut NecVx0, b: u16, c: u16, mut carry: bool) -> (u16, u16) { + let mut internal_counter; + let mut tmpa: u16; + let tmpb: u16 = b; + let mut tmpc: u16 = c; + let mut sigma_s: Self; + + (sigma_s, carry) = (tmpc as Self).alu_rcr(1, carry); // 17f: ZERO->tmpa | RRCY tmpc + tmpa = 0; + tmpc = sigma_s as u16; // 180: SIGMA->tmpc + internal_counter = Self::BITS - 1; // 180: MAXC + cpu.cycles_i(2, &[0x17f, 0x180]); + + // The main corx loop is between 181-186. + loop { + //println!(" >> impl corx: tmpa: {} tmpc: {}", tmpa, tmpc); + cpu.cycle_i(0x181); // 181: | NCY 8 (jump if no carry) + + if carry { + (sigma_s, carry, _, _) = (tmpa as Self).alu_add(tmpb as Self); // 182: | ADD tmpa + tmpa = sigma_s as u16; // 183: SIGMA->tmpa | F + cpu.cycles_i(2, &[0x182, 0x183]); + // SET FLAGS HERE + } + else { + // Jump delay for skipping to line 8 + cpu.cycle_i(MC_JUMP); + } + + (sigma_s, carry) = (tmpa as Self).alu_rcr(1, carry); // 184: | RRCY tmpa + tmpa = sigma_s as u16; // 185 + (sigma_s, carry) = (tmpc as Self).alu_rcr(1, carry); // 185: SIGMA->tmpa | RRCY tmpc + tmpc = sigma_s as u16; // 186: SIGMA->tmpc | NCZ 5 + + cpu.cycles_i(3, &[0x184, 0x185, 0x186]); + + if internal_counter == 0 { + break; // 186: no-jump + } + + // It's not explicitly explained where the internal counter is updated. + // I am just assuming it is decremented once per loop here. + internal_counter -= 1; + + cpu.cycle_i(MC_JUMP); // 186: (jump) 1 cycle delay to return to top of loop. + } + + // Fall through line 186 + cpu.cycles_i(2, &[0x187, MC_RTN]); // 187 'RTN', return delay cycle + + (tmpa, tmpc) + } + } + }; +} + +impl_corx!(u8); +impl_corx!(u16); + +pub trait CorNegate: Sized { + fn cor_negate(self, cpu: &mut NecVx0, b: u16, c: u16, neg_flag: bool, skip: bool) -> (u16, u16, u16, bool, bool); +} + +macro_rules! impl_cor_negate { + ($prim:ty) => { + impl CorNegate for $prim { + /// Implementation of the Microcode NEGATE co-routine used by signed multiplication and division. + /// Accepts tmpa (self), tmpb, tmpc, neg_flag (passed in first time by f1 flag), and skip which + /// effectively enters the NEGATE routine at line 7 (for division) + /// + /// Returns tmpa, tmpb, tmpc, carry and negate flag. + fn cor_negate( + self, + cpu: &mut NecVx0, + mut tmpb: u16, + mut tmpc: u16, + mut neg_flag: bool, + skip: bool, + ) -> (u16, u16, u16, bool, bool) { + let mut tmpa = self as u16; // Sign-extend + let mut sigma: u16; + let mut carry: bool; + let next_carry: bool; + + // Skip flag will skip to 1bb (line 7), such as when entering NEGATE from PREIDIV + if !skip { + (sigma, carry, _, _) = tmpc.alu_neg(); + //(sigma_s, carry, _, _) = (tmpc as Self).alu_neg(); // 1b6 + //sigma = sigma_s as u16; + tmpc = sigma; + + if carry { + sigma = !tmpa; // 1b8, jump, 1ba: SIGMA->tmpa | CF1 + cpu.cycles_i(5, &[0x1b6, 0x1b7, 0x1b8, MC_JUMP, 0x1ba]); + } + else { + (sigma, _, _, _) = tmpa.alu_neg(); + //(sigma_s, _, _, _) = (tmpa as Self).alu_neg(); // 1b8, 1b9, 1ba: SIGMA->tmpa | CF1 + //sigma = sigma_s as u16; + cpu.cycles_i(5, &[0x1b6, 0x1b7, 0x1b8, 0x1b9, 0x1ba]); + } + + tmpa = sigma; // 1ba + neg_flag = !neg_flag; // 1ba + } + + // 1bb: | LRCY tmpb + // 1bc: SIGMA->tmpb | NEG tmpb + //(_, carry) = rcl_u8_with_carry(tmpb as u8, 1, carry); // Set carry flag if tmpb is negative + carry = tmpb & (1 << (<$prim>::BITS - 1)) != 0; // LRCY is just checking msb of tmpb + + //println!(" NEGATE: tmpb: {:04X} carry: {}", tmpb, carry); + + (sigma, next_carry, _, _) = tmpb.alu_neg(); + //(sigma, next_carry, _, _) = (tmpb as Self).alu_neg(); + //sigma = sigma_s as u16; + + cpu.cycles_i(3, &[0x1bb, 0x1bc, 0x1bd]); + //println!(" NEGATE: a: {:04x} b: {:04x} c:{:04x} tmpb Carry flag is : {}", tmpa, tmpb, tmpc, carry); + // 1bd: | NCY 11 + if !carry { + // tmpb was positive + + // Jump to 11 + // 1bf: | RTN + cpu.cycles_i(3, &[MC_JUMP, 0x1bf, MC_RTN]); + } + else { + // tmpb was negative + //println!(" NEGATE: tmpb was negative"); + // 1be: SIGMA->tmpb | CF1 RTN + _ = next_carry; + tmpb = sigma; // tmpb = NEG tmpb + neg_flag = !neg_flag; // 1be + cpu.cycles_i(2, &[0x1be, MC_RTN]); + } + + (tmpa, tmpb, tmpc, carry, neg_flag) + } + } + }; +} + +impl_cor_negate!(u8); +impl_cor_negate!(u16); + +impl NecVx0 { + #[allow(dead_code)] + #[allow(unused_assignments)] // This isn't pretty but we are trying to mirror the microcode + /// Microcode routine for multiplication, 8 bit + /// Accepts al and 8-bit operand, returns 16 bit product (for AX) + pub fn mul8(&mut self, al: u8, operand: u8, signed: bool, mut negate: bool) -> u16 { + let mut sigma: u16; + let sigma8: u8; + + let mut tmpa: u16; + let mut tmpc: u16 = al as u16; // 150 A->tmpc | LRCY tmpc + let mut carry; + let zf; + + //(_, carry) = rcl_u8_with_carry(tmpc as u8, 1, carry); + carry = tmpc & 0x80 != 0; // LRCY is just checking MSB of tmpc + let mut tmpb: u16 = operand as u16; // 151: M->tmpb | X0 PREIMUL + self.cycles_i(2, &[0x150, 0x151]); + + // PREIMUL if signed == true + // ------------------------------------------------------------------------- + if signed { + // JMP PREIMUL + (sigma, _, _, _) = tmpc.alu_neg(); // 1c0: SIGMA->. | NEG tmpc + // 1c1 | NCY 7 + self.cycles_i(3, &[MC_JUMP, 0x1c0, 0x1c1]); + + if carry { + tmpc = sigma; + negate = !negate; // 1c2: SIGMA->tmpc | CF1 (flip F1 flag) + self.cycles_i(3, &[0x1c2, 0x1c3, MC_JUMP]); + } + else { + self.cycle_i(MC_JUMP); + } + + // Call negate with skip flag to enter at line 7 (tmpa unused) + (_, tmpb, tmpc, carry, negate) = (0u8).cor_negate(self, tmpb, tmpc, negate, true); + } + + // 152: | UNC CORX + self.cycles_i(2, &[0x152, MC_JUMP]); + + (tmpa, tmpc) = (tmpb as u8).corx(self, tmpb, tmpc, carry); + //let (accum, tmpc8) = self.corx8(tmpb as u8, tmpc as u8, carry); + + //println!("impl corx: {} {}, corx8: {} {}", tmpa2, tmpc2, accum, tmpc8); + //println!("corx: {}, {}", accum, tmpc8); + //tmpa = accum as u16; + //tmpc = tmpc8 as u16; + + // 153: | F1 NEGATE (REP prefix negates product) + self.cycle_i(0x153); + + // NEGATE if REP + // ------------------------------------------------------------------------- + if negate { + self.cycle_i(MC_JUMP); // Jump to NEGATE + //println!("PRE-NEG: a: {:04x} b: {:04x} c:{:04x} tmpb Carry flag is : {}", tmpa, tmpb, tmpc, carry); + (tmpa, tmpb, tmpc, carry, negate) = (tmpa as u8).cor_negate(self, tmpb, tmpc, negate, false); + //println!("POST-NEG2: a: {:04x} b: {:04x} c:{:04x} tmpb Carry flag is : {}", tmpa2, tmpb2, tmpc2, carry2); + } + + // 154: | X0 IMULCOF + // IMULFCOF if signed + // ------------------------------------------------------------------------- + self.cycle_i(0x154); + + if signed { + self.cycle_i(MC_JUMP); // JMP + tmpb = 0; + //(_, carry) = rcl_u8_with_carry(tmpc as u8, 1, carry); // Test if tmpc is negative + carry = tmpc & 0x80 != 0; // LRCY is just checking msb of tmpc + (sigma8, _, _, _) = (tmpa as u8).alu_adc(tmpb as u8, carry); + self.cycles_i(3, &[0x1cd, 0x1ce, 0x1cf]); + // SET FLAGS HERE + + // 1d0: | Z 8 + if sigma8 == 0 { + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.cycles_i(4, &[0x1d0, MC_JUMP, 0x1cc, MC_JUMP]); + } + else { + // 1d1: | SCOF RTN + self.set_flag(Flag::Carry); + self.set_flag(Flag::Overflow); + self.cycles_i(3, &[0x1d0, 0x1d1, MC_JUMP]); + } + + // 155: tmpc -> A | X0 7 + // JUMP + // 157: tmpa -> X | RNI + + //self.cycles_i(3, &[0x155, MC_JUMP, 0x157]); + self.cycles_i(2, &[0x155, MC_JUMP]); + + let product = tmpa << 8 | (tmpc & 0xFF); + return product; + } + + // 155: tmpc -> A | X0 7 + // 156: | UNC MULCOF + // JMP + + // MULCOF + // ------------------------------------------------------------------------- + // 1d2: | PASS tmpa (tmpa->sigma) + sigma = tmpa; + + // 1d3: SIGMA->. | UNC 12 | F (Set flags) + // JMP + + self.cycles_i(6, &[0x155, 0x156, MC_JUMP, 0x1d2, 0x1d3, MC_JUMP]); + zf = sigma == 0; + + // 1d0: | Z 8 (jump if zero) + if zf { + // JMP + // 1cc: | CCOF RTN + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.cycles_i(4, &[0x1d0, MC_JUMP, 0x1cc, MC_JUMP]); + } + else { + // 1d1: | SCOF RTN + self.set_flag(Flag::Carry); + self.set_flag(Flag::Overflow); + self.cycles_i(3, &[0x1d0, 0x1d1, MC_JUMP]); + } + + //self.cycle_i(0x157); // 157: tmpa-> X | RNI + + let product = tmpa << 8 | (tmpc & 0xFF); + product + } + + #[allow(unused_assignments)] // This isn't pretty but we are trying to mirror the microcode + /// Microcode routine for multiplication, 16 bit + /// Accepts ax and 16-bit operand, returns 32 bit product in two parts (for DX:AX) + pub fn mul16(&mut self, ax: u16, operand: u16, signed: bool, mut negate: bool) -> (u16, u16) { + let mut sigma: u16; + + let mut tmpa: u16; + let mut tmpc: u16 = ax; // 158 XA->tmpc | LRCY tmpc + let mut carry; + let zf; + + //(_, carry) = rcl_u16_with_carry(tmpc, 1, carry); // SIGMA isn't used? Just setting carry flag(?) + carry = tmpc & 0x8000 != 0; // LRCY is just checking msb + let mut tmpb: u16 = operand as u16; // 159: M->tmpb | X0 PREIMUL + self.cycles_i(2, &[0x158, 0x159]); + + // PREIMUL if signed == true + // ------------------------------------------------------------------------- + if signed { + // JMP PREIMUL + (sigma, _, _, _) = tmpc.alu_neg(); // 1c0: SIGMA->. | NEG tmpc + // 1c1 | NCY 7 + self.cycles_i(3, &[MC_JUMP, 0x1c0, 0x1c1]); + + if carry { + tmpc = sigma; + negate = !negate; // 1c2: SIGMA->tmpc | CF1 (flip F1 flag) + self.cycles_i(3, &[0x1c2, 0x1c3, MC_JUMP]); + } + else { + self.cycle_i(MC_JUMP); + } + + // Call negate with skip flag to enter at line 7 + (_, tmpb, tmpc, carry, negate) = 0u16.cor_negate(self, tmpb, tmpc, negate, true); + } + + // 15a: | UNC CORX + self.cycles_i(2, &[0x15a, MC_JUMP]); + + (tmpa, tmpc) = tmpb.corx(self, tmpb, tmpc, carry); + //(tmpa, tmpc) = self.corx16(tmpb, tmpc, carry); + //println!("a: {} c: {} , a: {} c: {}", tmpa2, tmpc2, tmpa, tmpc); + + // 15b: | F1 NEGATE (REP prefix negates product) + self.cycle_i(0x15b); + + // NEGATE if REP + // ------------------------------------------------------------------------- + if negate { + self.cycle_i(MC_JUMP); // Jump to NEGATE + (tmpa, tmpb, tmpc, carry, negate) = tmpa.cor_negate(self, tmpb, tmpc, negate, false); + } + + // 15c: | X0 IMULCOF + // IMULFCOF if signed + // ------------------------------------------------------------------------- + self.cycle_i(0x15c); + + if signed { + self.cycle_i(MC_JUMP); // JMP + tmpb = 0; // 1cd + //(_, carry) = rcl_u16_with_carry(tmpc, 1, carry); // Test if tmpc is negative + carry = tmpc & 0x8000 != 0; // 1cd: LRCY is just checking msb of tmpc + (sigma, _, _, _) = tmpa.alu_adc(tmpb, carry); + self.cycles_i(3, &[0x1cd, 0x1ce, 0x1cf]); + // Set flags here + + // 1d0: | Z 8 + if sigma == 0 { + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.cycles_i(4, &[0x1d0, MC_JUMP, 0x1cc, MC_JUMP]); + } + else { + // 1d1: | SCOF RTN + self.set_flag(Flag::Carry); + self.set_flag(Flag::Overflow); + self.cycles_i(3, &[0x1d0, 0x1d1, MC_JUMP]); + } + + // 15d: tmpc -> A | X0 7 + // JUMP + // 15f: tmpa -> X | RNI + //self.cycles_i(3, &[0x15d, MC_JUMP, 0x15f]); + self.cycles_i(2, &[0x15d, MC_JUMP]); + return (tmpa, tmpc); + } + // 15d: tmpc -> A | X0 7 + // 15e: | UNC MULCOF + // JMP + + // MULCOF + // ------------------------------------------------------------------------- + // 1d2: | PASS tmpa (tmpa->sigma) + sigma = tmpa; + // 1d3: SIGMA->. | UNC 12 | F (Set flags) + // JMP + self.cycles_i(6, &[0x15d, 0x15e, MC_JUMP, 0x1d2, 0x1d3, MC_JUMP]); + zf = sigma == 0; + + // 1d0: | Z 8 (jump if zero) + if zf { + // JMP + // 1cc: | CCOF RTN + self.clear_flag(Flag::Carry); + self.clear_flag(Flag::Overflow); + self.cycles_i(4, &[0x1d0, MC_JUMP, 0x1cc, MC_JUMP]); + } + else { + // 1d1: | SCOF RTN + self.set_flag(Flag::Carry); + self.set_flag(Flag::Overflow); + self.cycles_i(3, &[0x1d0, 0x1d1, MC_JUMP]); + } + + // 157: tmpa-> X | RNI + //self.cycle_i(0x157); + + (tmpa, tmpc) + } + + #[allow(dead_code)] + #[allow(unused_assignments)] // This isn't pretty but we are trying to mirror the microcode + /// Microcode routine for 8-bit division. + /// Accepts 16-bit dividend, 8-bit divisor. Returns 8 bit quotient and remainder, or Err() on divide error + /// so that an int0 can be triggered. + pub fn div8(&mut self, dividend: u16, divisor: u8, signed: bool, mut negate: bool) -> Result<(u8, u8), bool> { + let mut tmpa: u16 = dividend >> 8; // 160 + let mut tmpc: u16 = dividend & 0xFF; // 161 + let mut tmpb = divisor as u16; // 162 + + let mut sigma16: u16; + let sigma_next16: u16; + + let mut carry: bool; + let carry_next: bool; + + self.cycles_i(3, &[0x160, 0x161, 0x162]); + + //log::debug!(" div8: a: {:04x}, b: {:04x}, c: {:04x}, n: {}", tmpa, tmpb, tmpc, negate); + + // Is dividend negative? + (_, carry_next) = (tmpa as u8).alu_rcl(1, false); + + // Do PREIDIV if signed + if signed { + // 1b4: SIGMA->. | + //sigma16 = sigma8 as u16; + carry = carry_next; + + self.cycles_i(3, &[MC_JUMP, 0x1b4, 0x1b5]); + + // 1b5: | NCY 7 + if !carry { + // Dividend is positive + // Jump into NEGATE @ 7 (skip == true) + self.cycle_i(MC_JUMP); + (tmpa, tmpb, tmpc, _, negate) = (tmpa as u8).cor_negate(self, tmpb, tmpc, negate, true); + } + else { + // Dividend is negative + // Fall through to NEGATE + (tmpa, tmpb, tmpc, _, negate) = (tmpa as u8).cor_negate(self, tmpb, tmpc, negate, false); + } + + //log::debug!(" div8: post-negate: a: {:04x}, b: {:04x}, c: {:04x}, n: {}", tmpa, tmpb, tmpc, negate); + } + + // 163 + self.cycles_i(2, &[0x163, MC_JUMP]); + (tmpc, tmpa, carry) = match (tmpa as u8).cord(self, tmpa, tmpb, tmpc) { + Ok((tmpc, tmpa, carry)) => (tmpc, tmpa, carry), + Err(_) => { + return Err(false); + } + }; + + // 164 | COM1 tmpc + sigma16 = !tmpc; + + // 165 X->tmpb | X0 POSTDIV + tmpb = dividend >> 8; + + self.cycles_i(2, &[0x164, 0x165]); + + // Call POSTIDIV if signed + if signed { + self.cycles_i(2, &[MC_JUMP, 0x1c4]); + //log::debug!(" div8: POSTIDIV"); + + // 1c4: | NCY INT0 + if !carry { + self.cycle_i(MC_JUMP); + return Err(false); + } + + // 1c5: + (_, carry) = (tmpb as u8).alu_rcl(1, false); + // 1c6: + + //(sigma_next8, _, _, _) = (tmpa as u8).alu_neg(); + //sigma_next16 = sigma_next8 as u16; + (sigma_next16, _, _, _) = tmpa.alu_neg(); + + // 1c7: + + self.cycles_i(3, &[0x1c5, 0x1c6, 0x1c7]); + + if !carry { + // divisor is positive + self.cycle_i(MC_JUMP); // jump delay to 5 + } + else { + // divisor is negative + + //log::debug!(" div8: tmpb was negative in POSTIDIV"); + sigma16 = sigma_next16 as u16; // 1c8 SIGMA->tmpa + tmpa = sigma16; // if tmpb was negative (msb was set), set tmpa to NEG tempa (flip sign) + self.cycle_i(0x1c8); + } + + // 1c9 | INC tmpc + sigma16 = tmpc.wrapping_add(1) as u16; + + self.cycles_i(2, &[0x1c9, 0x1ca]); + // 1ca | F1 8 + if !negate { + //log::debug!(" div8: negate flag not set: tmpc = !tmpc"); + sigma16 = !tmpc; // 1cb: | COM tmpc + self.cycle_i(0x1cb); + } + else { + //log::debug!(" div8: negate flag was set: tmpc = NEG tmpa + 1"); + self.cycle_i(MC_JUMP); + } + + // clear carry, overflow flag here + self.cycles_i(2, &[0x1cc, MC_RTN]); + } + + tmpc = sigma16; // 166: SIGMA -> AL (Quotient) + + //log::debug!(" div8: done: a: {} b: {} c:{} ", tmpa, tmpb, tmpc); + + Ok((tmpc as u8, tmpa as u8)) + } + + /// Microcode routine for 16-bit division. + /// Accepts 32-bit dividend, 16-bit divisor. Returns 16 bit quotient and remainder, or Err() on divide error + /// so that an int0 can be triggered. + pub fn div16(&mut self, dividend: u32, divisor: u16, signed: bool, mut negate: bool) -> Result<(u16, u16), bool> { + let mut tmpa: u16 = (dividend >> 16) as u16; // 160 + let mut tmpc: u16 = (dividend & 0xFFFF) as u16; // 161 + let mut tmpb = divisor as u16; // 162 + + let mut sigma16: u16; + let sigma_next: u16; + + let mut carry: bool; + let carry_next: bool; + + self.cycles_i(3, &[0x168, 0x169, 0x16a]); + + //log::debug!(" div16: a: {:04x}, b: {:04x}, c: {:04x}, n: {}", tmpa, tmpb, tmpc, negate); + + (_, carry_next) = tmpa.alu_rcl(1, false); + + // Do PREIDIV if signed + if signed { + // 1b4: SIGMA->. | + //sigma16 = sigma8 as u16; + carry = carry_next; + + self.cycles_i(3, &[MC_JUMP, 0x1b4, 0x1b5]); + + // 1b5: | NCY 7 + if !carry { + // Jump into NEGATE @ 7 (skip == true) + self.cycle_i(MC_JUMP); + (tmpa, tmpb, tmpc, _, negate) = tmpa.cor_negate(self, tmpb, tmpc, negate, true); + } + else { + // Fall through to NEGATE + (tmpa, tmpb, tmpc, _, negate) = tmpa.cor_negate(self, tmpb, tmpc, negate, false); + } + + //log::debug!(" div16: post-negate: a: {:04x}, b: {:04x}, c: {:04x}, n: {}", tmpa, tmpb, tmpc, negate); + } + + // 16b: + self.cycles_i(2, &[0x163, MC_JUMP]); + (tmpc, tmpa, carry) = match tmpa.cord(self, tmpa, tmpb, tmpc) { + Ok((tmpc, tmpa, carry)) => (tmpc, tmpa, carry), + Err(_) => return Err(false), + }; + + // 16c | COM1 tmpc + sigma16 = !tmpc; + + // 16d DE->tmpb | X0 POSTDIV + tmpb = (dividend >> 16) as u16; + + self.cycles_i(2, &[0x16c, 0x16d]); + + // Call POSTIDIV if signed + if signed { + self.cycles_i(2, &[MC_JUMP, 0x1c4]); + //log::debug!(" div16: POSTIDIV"); + + // 1c4: | NCY INT0 + if !carry { + self.cycle_i(MC_JUMP); + return Err(false); + } + + // 1c5: + (_, carry) = tmpb.alu_rcl(1, false); + // 1c6: + + (sigma_next, _, _, _) = tmpa.alu_neg(); + // 1c7: + + self.cycles_i(3, &[0x1c5, 0x1c6, 0x1c7]); + + if !carry { + // divisor is positive + // jump delay to 5 + self.cycle_i(MC_JUMP); + } + else { + // divisor is negative + //log::debug!(" div16: tmpb was negative in POSTIDIV"); + sigma16 = sigma_next as u16; // 1c8 SIGMA->tmpa + tmpa = sigma16; // if tmpb was negative (msb was set), set tmpa to NEG tempa (flip sign) + self.cycle_i(0x1c8); + } + + // 1c9 | INC tmpc + sigma16 = tmpc.wrapping_add(1); + + self.cycles_i(2, &[0x1c9, 0x1ca]); + // 1ca | F1 8 + if !negate { + //log::debug!(" div16 negate flag not set: COM tmpc"); + sigma16 = !tmpc; // 1cb: | COM1 tmpc + self.cycle_i(0x1cb); + } + else { + //log::debug!(" div16: negate flag was set"); + self.cycle_i(MC_JUMP); + } + + // clear carry, overflow flag here + self.cycles_i(2, &[0x1cc, MC_RTN]); + } + + tmpc = sigma16; // 16e: SIGMA -> AX (Quotient) + + //log::debug!(" div16: done: a: {} b: {} c:{} ", tmpa, tmpb, tmpc); + + // 16f: tmpa -> DX (Remainder) + Ok((tmpc, tmpa)) + } +} diff --git a/core/src/cpu_vx0/queue.rs b/core/src/cpu_vx0/queue.rs new file mode 100644 index 00000000..df02a498 --- /dev/null +++ b/core/src/cpu_vx0/queue.rs @@ -0,0 +1,192 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::queue.rs + + Implements the data structure for the processor instruction queue. + +*/ + +use crate::cpu_vx0::*; + +pub struct InstructionQueue { + size: usize, + fetch_size: usize, + policy_size: usize, + len: usize, + back: usize, + front: usize, + q: [u8; QUEUE_MAX], + preload: Option, +} + +impl Default for InstructionQueue { + fn default() -> Self { + Self { + size: QUEUE_MAX, + fetch_size: 2, + policy_size: QUEUE_MAX - 2, + len: 0, + back: 0, + front: 0, + q: [0; QUEUE_MAX], + preload: None, + } + } +} + +impl InstructionQueue { + pub fn new(size: usize, fetch_size: usize) -> Self { + Self { + size, + fetch_size, + policy_size: size - fetch_size, + ..Self::default() + } + } + + pub fn set_size(&mut self, size: usize, fetch_size: usize) { + assert!(size <= QUEUE_MAX); + self.size = size; + self.fetch_size = fetch_size; + self.policy_size = size - fetch_size; + } + + #[inline] + pub fn at_policy_len(&self) -> bool { + self.len == self.policy_size + } + + #[inline] + pub fn has_room_for_fetch(&self) -> bool { + self.len <= self.policy_size + } + + #[inline] + pub fn len(&self) -> usize { + self.len + } + + #[inline] + pub fn len_p(&self) -> usize { + self.len + if self.preload.is_some() { 1 } else { 0 } + } + + #[allow(dead_code)] + #[inline] + pub fn is_full(&self) -> bool { + self.len == self.size + } + + #[inline] + pub fn get_preload(&mut self) -> Option { + let preload = self.preload; + self.preload = None; + preload + } + + #[inline] + pub fn has_preload(&self) -> bool { + self.preload.is_some() + } + + #[inline] + pub fn set_preload(&mut self) { + if self.len > 0 { + let byte = self.pop(); + self.preload = Some(byte); + } + else { + panic!("Tried to preload with empty queue.") + } + } + + #[inline] + pub fn push8(&mut self, byte: u8) { + if self.len < self.size { + self.q[self.front] = byte; + self.front = (self.front + 1) % self.size; + self.len += 1; + } + else { + panic!("Queue overrun!"); + } + } + + #[inline] + pub fn push16(&mut self, word: u16) { + assert_eq!(self.fetch_size, 2); + self.push8((word & 0xFF) as u8); + self.push8(((word >> 8) & 0xFF) as u8); + } + + #[inline] + pub fn pop(&mut self) -> u8 { + if self.len > 0 { + let byte = self.q[self.back]; + self.back = (self.back + 1) % self.size; + self.len -= 1; + + return byte; + } + panic!("Queue underrun!"); + } + + /// Flush the processor queue. This resets the queue to an empty state + pub fn flush(&mut self) { + self.len = 0; + self.back = 0; + self.front = 0; + self.preload = None; + } + + /// Convert the contents of the processor instruction queue to a hexadecimal string. + pub fn to_string(&self) -> String { + let mut base_str = "".to_string(); + + if let Some(preload) = self.preload { + base_str.push_str(&format!("{:02X}", preload)); + } + + for i in 0..self.len { + base_str.push_str(&format!("{:02X}", self.q[(self.back + i) % self.size])); + } + + base_str + } + + /// Write the contents of the processor instruction queue in order to the + /// provided slice of u8. The slice must be the same size as the current piq + /// length for the given cpu type. + #[allow(dead_code)] + pub fn to_slice(&self, slice: &mut [u8]) { + assert_eq!(self.size, slice.len()); + + for i in 0..self.len { + slice[i] = self.q[(self.back + i) % self.size]; + } + } +} diff --git a/core/src/cpu_vx0/stack.rs b/core/src/cpu_vx0/stack.rs new file mode 100644 index 00000000..4e810f8a --- /dev/null +++ b/core/src/cpu_vx0/stack.rs @@ -0,0 +1,176 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::stack.rs + + Implements stack-oriented routines such as push and pop. + +*/ + +use crate::{ + cpu_common::Segment, + cpu_vx0::{biu::*, *}, +}; + +impl NecVx0 { + pub fn push_u8(&mut self, data: u8, flag: ReadWriteFlag) { + // Stack pointer grows downwards + self.sp = self.sp.wrapping_sub(2); + self.biu_write_u8(Segment::SS, self.sp, data, flag); + } + + pub fn push_u16(&mut self, data: u16, flag: ReadWriteFlag) { + // Stack pointer grows downwards + self.sp = self.sp.wrapping_sub(2); + self.biu_write_u16(Segment::SS, self.sp, data, flag); + } + + pub fn pop_u16(&mut self) -> u16 { + let result = self.biu_read_u16(Segment::SS, self.sp, ReadWriteFlag::Normal); + + // Stack pointer shrinks upwards + self.sp = self.sp.wrapping_add(2); + result + } + + pub fn push_register16(&mut self, reg: Register16, flag: ReadWriteFlag) { + // Stack pointer grows downwards + self.sp = self.sp.wrapping_sub(2); + + let data = match reg { + Register16::AX => self.a.x(), + Register16::BX => self.b.x(), + Register16::CX => self.c.x(), + Register16::DX => self.d.x(), + Register16::SP => self.sp, + Register16::BP => self.bp, + Register16::SI => self.si, + Register16::DI => self.di, + Register16::CS => { + self.interrupt_inhibit = true; + self.cs + } + Register16::DS => { + self.interrupt_inhibit = true; + self.ds + } + Register16::SS => { + self.interrupt_inhibit = true; + self.ss + } + Register16::ES => { + self.interrupt_inhibit = true; + self.es + } + Register16::PC => self.pc, + _ => panic!("Invalid register"), + }; + + self.biu_write_u16(Segment::SS, self.sp, data, flag); + } + + pub fn pop_register16(&mut self, reg: Register16, flag: ReadWriteFlag) { + let data = self.biu_read_u16(Segment::SS, self.sp, flag); + + let mut update_sp = true; + match reg { + Register16::AX => self.set_register16(reg, data), + Register16::BX => self.set_register16(reg, data), + Register16::CX => self.set_register16(reg, data), + Register16::DX => self.set_register16(reg, data), + Register16::SP => { + self.sp = data; + update_sp = false; + } + Register16::BP => self.bp = data, + Register16::SI => self.si = data, + Register16::DI => self.di = data, + Register16::CS => { + self.cs = data; + self.interrupt_inhibit = true; + } + Register16::DS => { + self.ds = data; + self.interrupt_inhibit = true; + } + Register16::SS => { + self.ss = data; + self.interrupt_inhibit = true + } + Register16::ES => { + self.es = data; + self.interrupt_inhibit = true; + } + Register16::PC => self.pc = data, + _ => panic!("Invalid register"), + }; + // Stack pointer grows downwards + if update_sp { + self.sp = self.sp.wrapping_add(2); + } + } + + pub fn push_flags(&mut self, wflag: ReadWriteFlag) { + // Stack pointer grows downwards + self.sp = self.sp.wrapping_sub(2); + self.biu_write_u16(Segment::SS, self.sp, self.flags, wflag); + } + + pub fn pop_flags(&mut self) { + let result = self.biu_read_u16(Segment::SS, self.sp, ReadWriteFlag::Normal); + + let trap_was_set = self.get_flag(Flag::Trap); + let int_was_set = self.get_flag(Flag::Interrupt); + + // Ensure state of reserved flag bits + self.flags = result & FLAGS_POP_MASK; + self.flags |= CPU_FLAGS_RESERVED_ON; + + // Was interrupt flag just set? Set interrupt inhibit. + let int_is_set = self.get_flag(Flag::Interrupt); + if !int_was_set && int_is_set { + self.interrupt_inhibit = true; + } + + // Was trap flag just set? Set trap enable delay. + let trap_is_set = self.get_flag(Flag::Trap); + if !trap_was_set && trap_is_set { + self.trap_enable_delay = 2; + } + + // Was trap flag just disabled? Set trap disable delay. + if trap_was_set && !trap_is_set { + self.trap_disable_delay = 1; + } + + // Stack pointer grows downwards + self.sp = self.sp.wrapping_add(2); + } + + pub fn release(&mut self, disp: u16) { + self.sp = self.sp.wrapping_add(disp); + } +} diff --git a/core/src/cpu_vx0/step.rs b/core/src/cpu_vx0/step.rs new file mode 100644 index 00000000..e6268d13 --- /dev/null +++ b/core/src/cpu_vx0/step.rs @@ -0,0 +1,625 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::step.rs + + Implements a single instruction step for the 808x CPU. + +*/ + +use crate::{ + cpu_common::{CpuError, CpuException, ExecutionResult, StepResult}, + cpu_vx0::{decode::DECODE, *}, + vgdr, +}; + +impl NecVx0 { + /// Run a single instruction. + /// + /// We divide instruction execution into separate fetch/decode and microcode execution phases. + /// This is an artificial distinction, but allows for flexibility as the decode() function can be + /// used on anything that implements the ByteQueue trait, ie, raw memory for a disassembly viewer. + /// + /// REP string instructions are handled by stopping them after one iteration so that interrupts can + /// be checked. + pub fn step(&mut self, skip_breakpoint: bool) -> Result<(StepResult, u32), CpuError> { + self.instr_cycle = 0; + self.instr_elapsed = self.int_elapsed; + + // If tracing is enabled, clear the trace string vector that holds the trace from the last instruction. + if self.trace_enabled { + self.trace_str_vec.clear(); + self.trace_token_vec.clear(); + } + + // The Halt state can be expensive if we only execute one cycle per halt - however precise wake from halt is + // necessary for Area5150. We can dynamically adjust the cycle count of stepping in the halt state depending + // on a hint from the bus whether a timer interrupt is imminent. + if self.halted { + let halt_cycles = match self.bus().is_intr_imminent() { + true => 1, + false => 5, + }; + self.halt_cycles += halt_cycles as u64; + self.cycles(halt_cycles); + return Ok((StepResult::Normal, halt_cycles)); + } + + let mut instruction_address = self.instruction_address; + + // Fetch the next instruction unless we are executing a REP + if !self.in_rep { + // A real 808X CPU maintains a single Program Counter or PC register that points to the next instruction + // to be fetched, not the currently executing instruction. This value is "corrected" whenever the current + // value of IP is required, ie, pushing IP to the stack. This is performed by the 'CORR' microcode routine. + + // Sometimes it is more convenient for us to think of the current ip address, which can be calculated on the + // fly from PC by the ip() instruction, but only usefully on instruction boundaries, such as now. + self.instruction_ip = self.ip(); + self.instruction_address = NecVx0::calc_linear_address(self.cs, self.instruction_ip); + instruction_address = self.instruction_address; + //log::warn!("instruction address: {:05X}", instruction_address); + + if self.end_addr == (instruction_address as usize) { + return Ok((StepResult::ProgramEnd, 0)); + } + + // Check if we are in BreakpointHit state. This state must be cleared before we can execute another instruction. + if self.get_breakpoint_flag() { + return Ok((StepResult::BreakpointHit, 0)); + } + + // Check instruction address for breakpoint on execute flag + let iflags = self.bus.get_flags(instruction_address as usize); + if !skip_breakpoint && iflags & (MEM_BPE_BIT | MEM_SW_BIT) != 0 { + if iflags & MEM_SW_BIT != 0 { + // Stopwatch hit + for sw in self.stopwatches.iter_mut().flatten() { + // Check for stopwatch stop first. This is so we can restart on the same instruction, ie, + // when measuring loops where start==stop. + if sw.stop == instruction_address { + log::debug!("Stopwatch stop at {:05X}", instruction_address); + sw.stop(); + } + if sw.start == instruction_address && !sw.running { + log::debug!("Stopwatch start at {:05X}", instruction_address); + sw.start(); + } + } + self.update_sw_flag(); + } + + if iflags & MEM_BPE_BIT != 0 { + // Breakpoint hit + log::debug!("Breakpoint hit at {:05X}", instruction_address,); + self.set_breakpoint_flag(); + return Ok((StepResult::BreakpointHit, 0)); + } + } + + // Check for the step over breakpoint + if let Some(step_over_address) = self.step_over_breakpoint { + if instruction_address == step_over_address { + log::debug!("CPU: Step Over address hit: {:05X}", step_over_address); + // Clear the step over breakpoint, so it is not immediately re-triggered + self.step_over_breakpoint = None; + return Ok((StepResult::StepOverHit, 0)); + } + } + + // Check if this address is a return from a CALL or INT + if self.bus.get_flags(instruction_address as usize) & MEM_RET_BIT != 0 { + // This address is a return address, rewind the stack + self.rewind_call_stack(instruction_address); + } + + // Clear the validator cycle states from the last instruction. + #[cfg(feature = "cpu_validator")] + { + self.validate_init(); + } + + // If cycle tracing is enabled, we prefetch the current instruction directly from memory backend + // to make the instruction disassembly available to the trace log on the first byte fetch of an + // instruction. + // This of course now requires decoding each instruction twice, but cycle tracing is pretty slow + // anyway. + if self.trace_mode == TraceMode::CycleText { + self.bus.seek(instruction_address as usize); + self.i = match NecVx0::decode(&mut self.bus, true) { + Ok(i) => i, + Err(_) => { + self.is_running = false; + self.is_error = true; + return Err(CpuError::InstructionDecodeError(instruction_address)); + } + }; + //log::trace!("Fetching instruction..."); + self.i.address = instruction_address; + } + + // Fetch and decode the current instruction. This uses the CPU's own ByteQueue trait + // implementation, which fetches instruction bytes through the processor instruction queue. + //log::warn!("decoding instruction..."); + self.i = match NecVx0::decode(self, true) { + Ok(i) => i, + Err(_) => { + self.is_running = false; + self.is_error = true; + return Err(CpuError::InstructionDecodeError(instruction_address)); + } + }; + + // Begin the current instruction validation context. + #[cfg(feature = "cpu_validator")] + { + self.validate_begin(instruction_address); + } + } + + // Since Cpu::decode doesn't know anything about the current IP, it can't set it, so we do that now. + self.i.address = instruction_address; + + // Uncomment to debug instruction fetch + //self.debug_fetch(instruction_address); + + self.last_cs = self.cs; + self.last_ip = self.instruction_ip; + + // Load the mod/rm operand for the instruction, if applicable. + self.load_operand(); + + #[cfg(feature = "cpu_validator")] + { + (self.peek_fetch, _) = self.bus.read_u8(self.pc as usize, 0).unwrap(); + self.instr_slice = self.bus.get_vec_at(instruction_address as usize, self.i.size as usize); + } + + // Execute the current decoded instruction. + self.exec_result = self.execute_instruction(); + + let step_result = match &self.exec_result { + ExecutionResult::Okay => { + // Normal non-jump instruction updates CS:IP to next instruction during execute() + self.instruction_count += 1; + + // Perform instruction tracing, if enabled + if self.trace_enabled && self.trace_mode == TraceMode::Instruction { + self.trace_print(&self.instruction_state_string(self.last_cs, self.last_ip)); + } + + Ok((StepResult::Normal, self.device_cycles)) + } + ExecutionResult::OkayJump => { + // A control flow instruction updated PC. + self.instruction_count += 1; + self.jumped = true; + + // Perform instruction tracing, if enabled + if self.trace_enabled && self.trace_mode == TraceMode::Instruction { + self.trace_print(&self.instruction_state_string(self.last_cs, self.last_ip)); + } + + // Only CALLS will set a step over target. + if let Some(step_over_target) = self.step_over_target { + Ok((StepResult::Call(step_over_target), self.device_cycles)) + } + else { + Ok((StepResult::Normal, self.device_cycles)) + } + } + ExecutionResult::OkayRep => { + // We are in a REPx-prefixed instruction. + + // The ip will not increment until the instruction has completed, but + // continue to process interrupts. We passed pending_interrupt to execute + // earlier so that a REP string operation can call RPTI to be ready for + // an interrupt to occur. + + // REP will always set a step over target. + Ok((StepResult::Rep(self.step_over_target.unwrap()), self.device_cycles)) + } + /* + ExecutionResult::UnsupportedOpcode(o) => { + // This shouldn't really happen on the 8088 as every opcode does something, + // but allowed us to be missing opcode implementations during development. + self.is_running = false; + self.is_error = true; + Err(CpuError::UnhandledInstructionError(o, instruction_address)) + } + */ + ExecutionResult::ExecutionError(e) => { + // Something unexpected happened! + self.is_running = false; + self.is_error = true; + Err(CpuError::ExecutionError(instruction_address, e.to_string())) + } + ExecutionResult::Halt => { + // Specifically, this error condition is a halt with interrupts disabled - + // since only an interrupt can resume after a halt, execution cannot continue. + // This state is most often encountered during failed BIOS initialization checks. + self.is_running = false; + self.is_error = true; + Err(CpuError::CpuHaltedError(instruction_address)) + } + ExecutionResult::ExceptionError(exception) => { + // A CPU exception occurred. On the 8088, these are limited in scope to + // division errors, and overflow after INTO. + match exception { + CpuException::DivideError => { + // Moved int0 handling into aam/div instructions directly. + //self.handle_exception(0); + Ok((StepResult::Normal, self.device_cycles)) + } + _ => { + // Unhandled exception? + Err(CpuError::ExceptionError(*exception)) + } + } + } + }; + + // Reset interrupt pending flag - this flag is set on step_finish() and + // only valid for a single instruction execution. + self.intr_pending = false; + + step_result + } + + /// Finish the current CPU instruction. + /// + /// This function is meant to be called after devices are run after an instruction. + /// + /// Normally, this function will fetch the first byte of the next instruction. + /// Running devices can generate interrupts. If the INTR line is set by a device, + /// we do not want to fetch the next byte - we want to jump directly into the + /// interrupt routine - *unless* we are in a REP, in which case we set a flag + /// so that the interrupt execution can occur on the next call to step() to simulate + /// the string instruction calling RPTI. + /// + /// This function effectively simulates the RNI microcode routine. + pub fn step_finish(&mut self) -> Result { + let mut step_result = StepResult::Normal; + let mut irq = 7; + let mut did_interrupt = false; + let mut did_nmi = false; + let mut did_trap = false; + + // This function is called after devices are run for the CPU period, so reset device cycles. + // Device cycles will begin incrementing again with any terminating fetch. + self.instr_elapsed = 0; + self.int_elapsed = 0; + self.device_cycles = 0; + + if self.nmi && self.bus.nmi_enabled() && !self.nmi_triggered { + // NMI takes priority over trap and INTR. + if self.halted { + // Resume from halt on interrupt + self.resume(); + } + log::debug!("Triggered NMI!"); + self.nmi_triggered = true; + self.int2(); + did_nmi = true; + step_result = StepResult::Call(CpuAddress::Segmented(self.cs, self.ip())); + } + else if self.intr && self.interrupts_enabled() { + // An interrupt needs to be processed. + + if self.in_rep { + // We're in an REP prefixed-string instruction. + // Delay processing of the interrupt so that the string + // instruction can execute RPTI. At that point, the REP + // will terminate, and we can process the interrupt as normal. + self.intr_pending = true; + } + else { + // We are not in a REP prefixed string instruction, so we + // can process an interrupt normally. + + if self.halted { + // Resume from halt on interrupt + self.resume(); + } + + // Query the PIC to get the interrupt vector. + // This is a bit artificial as we don't actually read the IV during the 2nd + // INTA cycle like the CPU does, instead we save the value now and simulate it later. + // TODO: Think about changing this to query during INTA + if let Some(pic) = self.bus.pic_mut().as_mut() { + // Is INTR active? TODO: Could combine these calls (return Option) on query? + if pic.query_interrupt_line() { + if let Some(iv) = pic.get_interrupt_vector() { + irq = iv; + } + } + } + + // We will be jumping into an ISR now. Set the step result to Call and return + // the address of the next instruction. (Step Over skips ISRs) + step_result = StepResult::Call(CpuAddress::Segmented(self.cs, self.ip())); + + if self.int_flags[irq as usize] != 0 { + // This interrupt has a breakpoint + self.set_breakpoint_flag(); + } + self.hw_interrupt(irq); + did_interrupt = true; + self.biu_fetch_next(); + } + } + else if self.trap_enabled() { + // Trap has the lowest priority. + if self.halted { + // Resume from halt on trap + self.resume(); + } + self.int1(); + did_trap = true; + step_result = StepResult::Call(CpuAddress::Segmented(self.cs, self.ip())); + } + else if !self.halted { + // We didn't have NMI, INTR, or TRAP condition. Fetch the next instruction if not halted. + self.biu_fetch_next(); + } + + // If a CPU validator is enabled, validate the executed instruction. + #[cfg(feature = "cpu_validator")] + { + self.validate_instruction()?; + } + + let cur_intr = did_interrupt | did_nmi | did_trap; + + if self.instruction_history_on { + // Tick any stopwatches that are running. We maintain a single flag if one stopwatch + // is running so that we aren't always iterating through a vector of stopped watches. + // We also require instruction history, only for performance reasons to reduce if checks. + if self.stopwatch_running { + for sw in self.stopwatches.iter_mut().flatten() { + sw.tick(self.instr_cycle as u64); + } + } + + // Only add non-reentrant instructions to history, unless they were interrupted. + // This prevents spamming the history with multiple rep string operations. + if !self.instruction_reentrant || cur_intr { + if self.instruction_history.len() == CPU_HISTORY_LEN { + self.instruction_history.pop_front(); + } + + self.instruction_history.push_back(HistoryEntry::InstructionEntry { + cs: self.last_cs, + ip: self.last_ip, + cycles: self.instr_cycle as u16, + interrupt: self.last_intr, + jump: self.jumped, + i: self.i.clone(), + }); + } + + if did_nmi { + if self.instruction_history.len() == CPU_HISTORY_LEN { + self.instruction_history.pop_front(); + } + + self.instruction_history.push_back(HistoryEntry::NmiEntry { + cs: self.last_cs, + ip: self.last_ip, + }); + } + + if did_trap { + if self.instruction_history.len() == CPU_HISTORY_LEN { + self.instruction_history.pop_front(); + } + + self.instruction_history.push_back(HistoryEntry::TrapEntry { + cs: self.last_cs, + ip: self.last_ip, + }); + } + + if did_interrupt { + if self.instruction_history.len() == CPU_HISTORY_LEN { + self.instruction_history.pop_front(); + } + + self.instruction_history.push_back(HistoryEntry::InterruptEntry { + cs: self.last_cs, + ip: self.last_ip, + cycles: self.instr_cycle as u16, + iv: irq, + }); + } + + self.last_intr = cur_intr; + } + + Ok(step_result) + } + + #[rustfmt::skip] + #[allow(dead_code, unused_variables)] + pub fn debug_fetch(&mut self, instruction_address: u32) { + let (opcode, _cost) = self.bus.read_u8(instruction_address as usize, 0).expect("mem err"); + trace_print!(self, "Fetched instruction: {} op:{:02X} at [{:05X}]", self.i, opcode, self.i.address); + trace_print!(self, "Executing instruction: [{:04X}:{:04X}] {} ({})", self.cs, self.ip(), self.i, self.i.size); + log::warn!("Fetched instruction: {} op:{:02X} at [{:05X}]", self.i, opcode, self.i.address); + //log::warn!("Executing instruction: [{:04X}:{:04X}] {} ({})", self.cs, self.ip, self.i, self.i.size); + } + + #[cfg(feature = "cpu_validator")] + pub fn validate_init(&mut self) { + if self.validator_state == CpuValidatorState::Running { + if let Some(ref mut validator) = self.validator { + validator.reset_instruction(); + } + self.cycle_states.clear(); + } + else { + // Clear cycle states spent in reset but not initial prefetch + self.clear_reset_cycle_states(); + } + + self.vregs = self.get_vregisters(); + } + + #[cfg(feature = "cpu_validator")] + pub fn validate_begin(&mut self, instruction_address: u32) { + let v_address = NecVx0::calc_linear_address(self.vregs.cs, self.vregs.ip); + if v_address != instruction_address { + log::warn!( + "Validator address mismatch: {:05X} != {:05X}", + v_address, + instruction_address + ); + } + + if self.vregs.flags & CPU_FLAG_TRAP != 0 { + log::warn!("Trap flag is set - may break validator!"); + } + + if let Some(ref mut validator) = self.validator { + if (instruction_address as usize) == self.validator_end { + log::info!("Validation reached end address. Stopping."); + self.validator_state = CpuValidatorState::Ended; + } + + if self.validator_state == CpuValidatorState::Uninitialized + || self.validator_state == CpuValidatorState::Running + { + validator.begin_instruction( + &self.vregs, + (instruction_address + self.i.size) as usize & 0xFFFFF, + self.validator_end, + ); + } + } + } + + #[cfg(feature = "cpu_validator")] + pub fn validate_instruction(&mut self) -> Result<(), CpuError> { + match self.exec_result { + ExecutionResult::Okay + | ExecutionResult::OkayJump + | ExecutionResult::ExceptionError(CpuException::DivideError) => { + let mut v_flags = 0; + + if let ExecutionResult::ExceptionError(CpuException::DivideError) = self.exec_result { + // In the case of a divide exception, undefined flags get pushed to the stack. + // So until we figure out the actual logic behind setting those undefined flags, + // we can't validate writes. Also the cycle timing seems to vary a little when + // executing int0, so allow a one cycle variance. + v_flags |= VAL_NO_WRITES | VAL_NO_FLAGS | VAL_ALLOW_ONE; + } + + match self.i.mnemonic { + Mnemonic::DIV => { + // There's a one cycle variance in my DIV instructions somewhere. + // I just want to get these tests out the door, so allow it. + v_flags |= VAL_ALLOW_ONE; + } + Mnemonic::IDIV => { + v_flags |= VAL_NO_WRITES | VAL_NO_FLAGS | VAL_NO_CYCLES; + } + _ => {} + } + + // End validation of current instruction + let vregs = self.get_vregisters(); + + if self.i.size == 0 { + log::error!("Invalid length: [{:05X}] {}", self.instruction_address, self.i); + } + + let cpu_address = self.flat_ip() as usize; + + if let Some(ref mut validator) = self.validator { + // If validator uninitialized, set register state now and move into running state. + if self.validator_state == CpuValidatorState::Uninitialized { + // This resets the validator CPU + log::debug!("Validator Uninitialized. Resetting validator and setting registers..."); + validator.set_regs(); + self.validator_state = CpuValidatorState::Running; + } + + if self.validator_state == CpuValidatorState::Running { + log::debug!("Validating opcode: {:02X}", self.i.opcode); + match validator.validate_instruction( + self.i.to_string(), + &self.instr_slice, + v_flags, + self.peek_fetch as u16, + vgdr!(self.i).has_modrm(), + 0, + &vregs, + &self.cycle_states, + ) { + Ok(result) => { + match result { + ValidatorResult::Ok => {} + ValidatorResult::OkEnd => { + if self.validator_end == cpu_address { + self.validator_state = CpuValidatorState::Ended; + + // Validation has reached program end address + if let Err(e) = validator.validate_regs(&vregs) { + log::warn!("Register validation failure: {} Halting execution.", e); + self.is_running = false; + self.is_error = true; + return Err(CpuError::CpuHaltedError(self.instruction_address)); + } + else { + log::debug!("Registers validated. Validation ended successfully."); + self.validator_state = CpuValidatorState::Ended; + self.trace_flush(); + } + } + } + _ => { + log::warn!("Validation failure: Halting execution."); + self.is_running = false; + self.is_error = true; + return Err(CpuError::CpuHaltedError(self.instruction_address)); + } + } + } + Err(e) => { + log::warn!("Validation failure: {} Halting execution.", e); + self.is_running = false; + self.is_error = true; + return Err(CpuError::CpuHaltedError(self.instruction_address)); + } + } + } + } + } + _ => {} + } + Ok(()) + } +} diff --git a/core/src/cpu_vx0/string.rs b/core/src/cpu_vx0/string.rs new file mode 100644 index 00000000..6205066a --- /dev/null +++ b/core/src/cpu_vx0/string.rs @@ -0,0 +1,339 @@ +/* + MartyPC + https://github.com/dbalsom/martypc + + Copyright 2022-2024 Daniel Balsom + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the “Software”), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------------------------- + + cpu_vx0::string.rs + + Implements string operations. + +*/ + +use crate::{ + cpu_common::{alu::AluSub, Mnemonic, Segment}, + cpu_vx0::*, +}; + +impl NecVx0 { + pub fn string_op(&mut self, opcode: Mnemonic, segment_override: Option) { + let segment_base_ds = segment_override.unwrap_or(Segment::DS); + + match opcode { + Mnemonic::STOSB => { + // STOSB - Write AL to [es:di] (ES prefix cannot be overridden) + // No flags affected + + // Write AL to [es:di] + self.biu_write_u8(Segment::ES, self.di, self.a.l(), ReadWriteFlag::Normal); + + match self.get_flag(Flag::Direction) { + false => { + // Direction flag clear, process forwards + self.di = self.di.wrapping_add(1); + } + true => { + // Direction flag set, process backwards + self.di = self.di.wrapping_sub(1); + } + } + } + Mnemonic::STOSW => { + // STOSW - Write AX to [es:di] (ES prefix cannot be overridden) + // No flags affected + + // Write AX to [es:di] + self.biu_write_u16(Segment::ES, self.di, self.a.x(), ReadWriteFlag::Normal); + + match self.get_flag(Flag::Direction) { + false => { + // Direction flag clear, process forwards + self.di = self.di.wrapping_add(2); + } + true => { + // Direction flag set, process backwards + self.di = self.di.wrapping_sub(2); + } + } + } + Mnemonic::LODSB => { + // LODSB affects no flags + // Store byte [ds:si] in AL (Segment overrideable) + + let data = self.biu_read_u8(segment_base_ds, self.si); + + self.set_register8(Register8::AL, data); + + // Increment or Decrement SI according to Direction flag + match self.get_flag(Flag::Direction) { + false => { + // Direction flag clear, process forwards + self.si = self.si.wrapping_add(1); + } + true => { + // Direction flag set, process backwards + self.si = self.si.wrapping_sub(1); + } + } + } + Mnemonic::LODSW => { + // LODSW affects no flags + // Store word [ds:si] in AX (Segment overrideable) + let data = self.biu_read_u16(segment_base_ds, self.si, ReadWriteFlag::Normal); + + self.set_register16(Register16::AX, data); + + // Increment or Decrement SI according to Direction flag + match self.get_flag(Flag::Direction) { + false => { + // Direction flag clear, process forwards + self.si = self.si.wrapping_add(2); + } + true => { + // Direction flag set, process backwards + self.si = self.si.wrapping_sub(2); + } + } + } + Mnemonic::MOVSB => { + // Store byte from [ds:si] in [es:di] (DS Segment overrideable) + + let data = self.biu_read_u8(segment_base_ds, self.si); + self.cycle_i(0x12e); + self.biu_write_u8(Segment::ES, self.di, data, ReadWriteFlag::Normal); + + match self.get_flag(Flag::Direction) { + false => { + // Direction flag clear, process forwards + self.si = self.si.wrapping_add(1); + self.di = self.di.wrapping_add(1); + } + true => { + // Direction flag set, process backwards + self.si = self.si.wrapping_sub(1); + self.di = self.di.wrapping_sub(1); + } + } + } + Mnemonic::MOVSW => { + // Store word from [ds:si] in [es:di] (DS Segment overrideable) + + let data = self.biu_read_u16(segment_base_ds, self.si, ReadWriteFlag::Normal); + self.cycle_i(0x12e); + self.biu_write_u16(Segment::ES, self.di, data, ReadWriteFlag::Normal); + + match self.get_flag(Flag::Direction) { + false => { + // Direction flag clear, process forwards + self.si = self.si.wrapping_add(2); + self.di = self.di.wrapping_add(2); + } + true => { + // Direction flag set, process backwards + self.si = self.si.wrapping_sub(2); + self.di = self.di.wrapping_sub(2); + } + } + } + Mnemonic::SCASB => { + // SCASB: Compare byte from [es:di] with value in AL. + // Flags: o..szapc + // Override: ES cannot be overridden + + self.cycles_i(2, &[0x121, MC_JUMP]); + let data = self.biu_read_u8(Segment::ES, self.di); + self.cycles_i(3, &[0x126, 0x127, 0x128]); + + let (result, carry, overflow, aux_carry) = self.a.l().alu_sub(data); + // Test operation behaves like CMP + self.set_flag_state(Flag::Carry, carry); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u8(result); + + match self.get_flag(Flag::Direction) { + false => { + // Direction flag clear, process forwards + self.di = self.di.wrapping_add(1); + } + true => { + // Direction flag set, process backwards + self.di = self.di.wrapping_sub(1); + } + } + } + Mnemonic::SCASW => { + // SCASW: Compare word from [es:di] with value in AX. + // Flags: o..szapc + // Override: ES cannot be overridden + + self.cycles_i(2, &[0x121, MC_JUMP]); + let data = self.biu_read_u16(Segment::ES, self.di, ReadWriteFlag::Normal); + self.cycles_i(3, &[0x126, 0x127, 0x128]); + + let (result, carry, overflow, aux_carry) = self.a.x().alu_sub(data); + // Test operation behaves like CMP + self.set_flag_state(Flag::Carry, carry); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u16(result); + + match self.get_flag(Flag::Direction) { + false => { + // Direction flag clear, process forwards + self.di = self.di.wrapping_add(2); + } + true => { + // Direction flag set, process backwards + self.di = self.di.wrapping_sub(2); + } + } + } + Mnemonic::CMPSB => { + // CMPSB: Compare bytes from [es:di] to [ds:si] + // Flags: The CF, OF, SF, ZF, AF, and PF flags are set according to the temporary result of the comparison. + // Override: DS can be overridden + + self.cycle_i(0x121); + let dssi_op = self.biu_read_u8(segment_base_ds, self.si); + self.cycles_i(2, &[0x123, 0x124]); + let esdi_op = self.biu_read_u8(Segment::ES, self.di); + self.cycles_i(3, &[0x126, 0x127, 0x128]); + + let (result, carry, overflow, aux_carry) = dssi_op.alu_sub(esdi_op); + + // Test operation behaves like CMP + self.set_flag_state(Flag::Carry, carry); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u8(result); + + match self.get_flag(Flag::Direction) { + false => { + // Direction flag clear, process forwards + self.si = self.si.wrapping_add(1); + self.di = self.di.wrapping_add(1); + } + true => { + // Direction flag set, process backwards + self.si = self.si.wrapping_sub(1); + self.di = self.di.wrapping_sub(1); + } + } + } + Mnemonic::CMPSW => { + // CMPSW: Compare words from [es:di] to [ds:si] + // Flags: The CF, OF, SF, ZF, AF, and PF flags are set according to the temporary result of the comparison. + // Override: DS can be overridden + + self.cycle_i(0x121); + let dssi_op = self.biu_read_u16(segment_base_ds, self.si, ReadWriteFlag::Normal); + self.cycles_i(2, &[0x123, 0x124]); + let esdi_op = self.biu_read_u16(Segment::ES, self.di, ReadWriteFlag::Normal); + self.cycles_i(3, &[0x126, 0x127, 0x128]); + + let (result, carry, overflow, aux_carry) = dssi_op.alu_sub(esdi_op); + + // Test operation behaves like CMP + self.set_flag_state(Flag::Carry, carry); + self.set_flag_state(Flag::Overflow, overflow); + self.set_flag_state(Flag::AuxCarry, aux_carry); + self.set_szp_flags_from_result_u16(result); + + match self.get_flag(Flag::Direction) { + false => { + // Direction flag clear, process forwards + self.si = self.si.wrapping_add(2); + self.di = self.di.wrapping_add(2); + } + true => { + // Direction flag set, process backwards + self.si = self.si.wrapping_sub(2); + self.di = self.di.wrapping_sub(2); + } + } + } + _ => { + panic!("CPU: Unhandled opcode to string_op(): {:?}", opcode); + } + } + } + + /// Implement the RPTS microcode co-routine for string operation repetition. + pub fn rep_start(&mut self) -> bool { + if !self.rep_init { + // First entry into REP-prefixed instruction, run the first line where we + // decide whether to call RPTS + match self.i.mnemonic { + Mnemonic::MOVSB | Mnemonic::MOVSW => self.cycle_i(0x12c), + Mnemonic::CMPSB | Mnemonic::CMPSW => self.cycle_i(0x120), + Mnemonic::STOSB | Mnemonic::STOSW => self.cycle_i(0x11c), + Mnemonic::LODSB | Mnemonic::LODSW => self.cycle_i(0x12c), + Mnemonic::SCASB | Mnemonic::SCASW => self.cycle_i(0x120), + _ => {} + } + + if self.in_rep { + // Rep-prefixed instruction is starting for the first time. Run the RPTS procedure. + if self.c.x() == 0 { + self.cycles_i(4, &[MC_JUMP, 0x112, 0x113, 0x114]); + self.rep_end(); + return false; + } + else { + // CX > 0. Load ALU for decrementing CX + self.cycles_i(7, &[MC_JUMP, 0x112, 0x113, 0x114, MC_JUMP, 0x116, MC_RTN]); + } + + // Mark this instruction as reentrant - step will execute a single iteration + self.instruction_reentrant = true; + } + } + + self.rep_init = true; + true + } + + pub fn rep_end(&mut self) { + self.rep_init = false; + self.in_rep = false; + self.rep_type = RepType::NoRep; + } + + /// Implement the RPTI microcode co-routine for string interrupt handling. + pub fn rep_interrupt(&mut self) { + self.biu_fetch_suspend(); + self.cycles_i(2, &[0x118, 0x119]); + self.corr(); + self.cycle_i(0x11a); + self.biu_queue_flush(); + + // Rewind IP so that it points to REP instruction again afterward. + // This behavior will emulate the 8088's bug with string operations and segment overrides, + // as the next time the instruction is fetched it will be with only a single prefix. + self.pc = self.pc.wrapping_sub(2); + + self.rep_end(); + // Flush was on RNI so no extra cycle here + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index b951a497..d4aa3d2c 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -41,6 +41,7 @@ pub mod bytequeue; pub mod coreconfig; pub mod cpu_808x; pub mod cpu_common; +pub mod cpu_vx0; pub mod device_traits; pub mod device_types; pub mod devices; diff --git a/core/src/machine.rs b/core/src/machine.rs index 7f956005..e93e1936 100644 --- a/core/src/machine.rs +++ b/core/src/machine.rs @@ -33,7 +33,7 @@ the appropriate methods on Bus. */ -use crate::cpu_common::{CpuDispatch, CpuSubType}; +use crate::cpu_common::{CpuAddress, CpuDispatch, CpuSubType, ServiceEvent, StepResult}; use log; use anyhow::{anyhow, Error}; @@ -49,8 +49,8 @@ use crate::{ breakpoints::BreakPointType, bus::{BusInterface, ClockFactor, DeviceEvent, MEM_CP_BIT}, coreconfig::CoreConfig, - cpu_808x::{Intel808x, CpuAddress, CpuError, ServiceEvent, StepResult}, - cpu_common::{Cpu, CpuOption, CpuType, TraceMode}, + cpu_808x::{Intel808x}, + cpu_common::{Cpu, CpuOption, CpuError, CpuType, TraceMode}, device_traits::videocard::{VideoCard, VideoCardId, VideoCardInterface, VideoCardState, VideoOption}, devices::{ dma::DMAControllerStringState, @@ -499,7 +499,7 @@ impl Machine { #[cfg(feature = "cpu_validator")] { cpu = match CpuBuilder::new() - .with_cpu_type(CpuType::Intel808x) + .with_cpu_type(CpuType::Intel8088) .with_cpu_subtype(CpuSubType::Intel8088) .with_trace_mode(trace_mode) .with_trace_logger(trace_logger) diff --git a/core/src/machine_config.rs b/core/src/machine_config.rs index cbaa4473..95e90deb 100644 --- a/core/src/machine_config.rs +++ b/core/src/machine_config.rs @@ -181,7 +181,7 @@ impl Default for MachineDescriptor { timer_crystal: None, bus_crystal: IBM_PC_SYSTEM_CLOCK, open_bus_byte: 0xFF, - cpu_type: CpuType::Intel808x, + cpu_type: CpuType::Intel8088, cpu_factor: ClockFactor::Divisor(3), cpu_turbo_factor: ClockFactor::Divisor(2), bus_type: BusType::Isa8, @@ -233,7 +233,7 @@ lazy_static! { system_crystal: IBM_PC_SYSTEM_CLOCK, timer_crystal: None, bus_crystal: IBM_PC_SYSTEM_CLOCK, - cpu_type: CpuType::Intel808x, + cpu_type: CpuType::Intel8088, cpu_factor: ClockFactor::Divisor(3), cpu_turbo_factor: ClockFactor::Divisor(2), bus_type: BusType::Isa8, @@ -256,7 +256,7 @@ lazy_static! { timer_crystal: None, bus_crystal: IBM_PC_SYSTEM_CLOCK, open_bus_byte: 0xE8, - cpu_type: CpuType::Intel808x, + cpu_type: CpuType::Intel8088, cpu_factor: ClockFactor::Divisor(3), cpu_turbo_factor: ClockFactor::Divisor(2), bus_type: BusType::Isa8, diff --git a/frontends/martypc_desktop_wgpu/src/cpu_test/common.rs b/frontends/martypc_desktop_wgpu/src/cpu_test/common.rs index 95ca5735..904be2ae 100644 --- a/frontends/martypc_desktop_wgpu/src/cpu_test/common.rs +++ b/frontends/martypc_desktop_wgpu/src/cpu_test/common.rs @@ -32,7 +32,7 @@ use anyhow::{bail, Error}; use colored::Colorize; -use marty_core::cpu_808x::*; +use marty_core::{cpu_808x::*, cpu_common::QueueOp}; use std::{ collections::{HashMap, LinkedList}, ffi::OsString, diff --git a/frontends/martypc_desktop_wgpu/src/cpu_test/gen_tests.rs b/frontends/martypc_desktop_wgpu/src/cpu_test/gen_tests.rs index 8cc876cf..eb231f38 100644 --- a/frontends/martypc_desktop_wgpu/src/cpu_test/gen_tests.rs +++ b/frontends/martypc_desktop_wgpu/src/cpu_test/gen_tests.rs @@ -39,28 +39,38 @@ use std::{ }; use config_toml_bpaf::ConfigFileParams; +use serde::{Deserialize, Serialize}; use marty_core::{ arduino8088_validator::ArduinoValidator, bytequeue::ByteQueue, - cpu_808x::{mnemonic::Mnemonic, Cpu, *}, - cpu_common::{CpuOption, CpuType, TraceMode}, - cpu_validator::{BusCycle, BusOp, BusOpType, BusState, CpuValidator, CycleState}, + cpu_808x::{Cpu, *}, + cpu_common, + cpu_common::{ + builder::CpuBuilder, + CpuAddress, + CpuOption, + CpuSubType, + CpuType, + Mnemonic, + Register16, + Register8, + TraceMode, + }, + cpu_validator::{BusCycle, BusOp, BusOpType, BusState, CpuValidator, CycleState, ValidatorMode, ValidatorType}, devices::pic::Pic, tracelogger::TraceLogger, }; use crate::cpu_test::common::{clean_cycle_states, write_tests_to_file, CpuTest, TestState}; -use serde::{Deserialize, Serialize}; - pub fn run_gentests(config: &ConfigFileParams) { //let pic = Rc::new(RefCell::new(Pic::new())); // Create the cpu trace file, if specified - let mut cpu_trace = TraceLogger::None; + let mut trace_logger = TraceLogger::None; if let Some(trace_filename) = &config.machine.cpu.trace_file { - cpu_trace = TraceLogger::from_filename(&trace_filename); + trace_logger = TraceLogger::from_filename(&trace_filename); } // Create the validator trace file, if specified @@ -69,24 +79,33 @@ pub fn run_gentests(config: &ConfigFileParams) { validator_trace = TraceLogger::from_filename(&trace_filename); } - #[cfg(feature = "cpu_validator")] - use marty_core::cpu_validator::ValidatorMode; - let trace_mode = config.machine.cpu.trace_mode.unwrap_or_default(); - let mut cpu = Cpu::new( - CpuType::Intel8088, - trace_mode, - cpu_trace, - #[cfg(feature = "cpu_validator")] - config.validator.vtype.unwrap(), - #[cfg(feature = "cpu_validator")] - validator_trace, - #[cfg(feature = "cpu_validator")] - ValidatorMode::Instruction, - #[cfg(feature = "cpu_validator")] - config.validator.baud_rate.unwrap_or(1_000_000), - ); + let mut cpu; + #[cfg(feature = "cpu_validator")] + { + cpu = match CpuBuilder::new() + .with_cpu_type(config.tests.test_cpu_type.unwrap_or(CpuType::Intel8088)) + //.with_cpu_subtype(CpuSubType::Intel8088) + .with_trace_mode(trace_mode) + .with_trace_logger(trace_logger) + .with_validator_type(ValidatorType::Arduino8088) + .with_validator_mode(ValidatorMode::Instruction) + .with_validator_logger(validator_trace) + .with_validator_baud(config.validator.baud_rate.unwrap_or(1_000_000)) + .build() + { + Ok(cpu) => cpu, + Err(e) => { + log::error!("Failed to build CPU: {}", e); + std::process::exit(1); + } + } + }; + #[cfg(not(feature = "cpu_validator"))] + { + panic!("Validator feature not enabled!") + }; if let Some(seed) = config.tests.test_seed { log::debug!("Using random seed from config: {}", seed); @@ -277,12 +296,14 @@ pub fn run_gentests(config: &ConfigFileParams) { cpu.randomize_mem(); cpu.randomize_regs(); - let mut instruction_address = Cpu::calc_linear_address(cpu.get_register16(Register16::CS), cpu.ip()); + let mut instruction_address = + cpu_common::calc_linear_address(cpu.get_register16(Register16::CS), cpu.get_ip()); - while (cpu.ip() > 0xFFF0) || ((instruction_address & 0xFFFFF) > 0xFFFF0) { + while (cpu.get_ip() > 0xFFF0) || ((instruction_address & 0xFFFFF) > 0xFFFF0) { // Avoid IP wrapping issues for now cpu.randomize_regs(); - instruction_address = Cpu::calc_linear_address(cpu.get_register16(Register16::CS), cpu.ip()); + instruction_address = + cpu_common::calc_linear_address(cpu.get_register16(Register16::CS), cpu.get_ip()); } test_num += 1; @@ -295,24 +316,28 @@ pub fn run_gentests(config: &ConfigFileParams) { cpu.random_inst_from_opcodes(&[test_opcode]); } - if cpu.ip() != cpu.get_register16(Register16::PC) { - log::error!("IP: {:04X} PC: {:04X}", cpu.ip(), cpu.get_register16(Register16::PC)); + if cpu.get_ip() != cpu.get_register16(Register16::PC) { + log::error!( + "IP: {:04X} PC: {:04X}", + cpu.get_ip(), + cpu.get_register16(Register16::PC) + ); panic!("IP and PC are out of sync!"); } // Decode this instruction - instruction_address = Cpu::calc_linear_address(cpu.get_register16(Register16::CS), cpu.ip()); + instruction_address = cpu_common::calc_linear_address(cpu.get_register16(Register16::CS), cpu.get_ip()); log::debug!( "Instruction address: {:05X} [{:04X}:{:04X}]", instruction_address, cpu.get_register16(Register16::CS), - cpu.ip() + cpu.get_ip() ); cpu.bus_mut().seek(instruction_address as usize); let opcode = cpu.bus().peek_u8(instruction_address as usize).expect("mem err"); - let mut i = match Cpu::decode(cpu.bus_mut(), true) { + let mut i = match cpu.get_type().decode(cpu.bus_mut(), true) { Ok(i) => i, Err(_) => { log::error!("Instruction decode error, skipping..."); @@ -339,10 +364,13 @@ pub fn run_gentests(config: &ConfigFileParams) { ); // Set terminating address for CPU validator. - let end_address = - Cpu::calc_linear_address(cpu.get_register16(Register16::CS), cpu.ip().wrapping_add(i.size as u16)); + let end_address = cpu_common::calc_linear_address( + cpu.get_register16(Register16::CS), + cpu.get_ip().wrapping_add(i.size as u16), + ); - cpu.set_end_address(end_address as usize); + //log::debug!("Setting end address: {:05X}", end_address); + cpu.set_end_address(CpuAddress::Flat(end_address)); log::trace!("Setting end address: {:05X}", end_address); match i.mnemonic { @@ -532,7 +560,7 @@ pub fn initial_state_from_ops( let mut pc = ip; for byte in instr_bytes { - let flat_addr = Cpu::calc_linear_address(cs, pc); + let flat_addr = cpu_common::calc_linear_address(cs, pc); code_addresses.insert(flat_addr, (*byte, true)); initial_state.insert(flat_addr, *byte); pc = pc.wrapping_add(1); diff --git a/frontends/martypc_desktop_wgpu/src/cpu_test/process_tests.rs b/frontends/martypc_desktop_wgpu/src/cpu_test/process_tests.rs index ed393267..c046063e 100644 --- a/frontends/martypc_desktop_wgpu/src/cpu_test/process_tests.rs +++ b/frontends/martypc_desktop_wgpu/src/cpu_test/process_tests.rs @@ -30,14 +30,7 @@ #![allow(warnings, unused)] -use crate::cpu_test::common::{ - is_prefix_in_vec, - opcode_extension_from_path, - opcode_from_path, - read_tests_from_file, - write_tests_to_file, - TestFileLoad, -}; +use marty_core::cpu_common; use std::{ collections::{HashMap, LinkedList}, ffi::OsString, @@ -49,22 +42,40 @@ use std::{ time::{Duration, Instant}, }; +use colored::*; +use config_toml_bpaf::{ConfigFileParams, TestMode}; +use flate2::read::GzDecoder; +use serde::{Deserialize, Serialize}; + +use crate::cpu_test::common::{ + is_prefix_in_vec, + opcode_extension_from_path, + opcode_from_path, + read_tests_from_file, + write_tests_to_file, + CpuTest, + TestFileLoad, + TestState, +}; + use marty_core::{ bytequeue::ByteQueue, - cpu_808x::{mnemonic::Mnemonic, Cpu, *}, - cpu_common::{CpuOption, CpuType, TraceMode}, - cpu_validator::{BusCycle, BusOp, BusOpType, BusState, CpuValidator, CycleState, VRegisters, ValidatorType}, + cpu_808x::{Cpu, *}, + cpu_common::{builder::CpuBuilder, CpuAddress, CpuOption, CpuSubType, CpuType, Mnemonic, Register16, TraceMode}, + cpu_validator::{ + BusCycle, + BusOp, + BusOpType, + BusState, + CpuValidator, + CycleState, + VRegisters, + ValidatorMode, + ValidatorType, + }, tracelogger::TraceLogger, }; -use config_toml_bpaf::{ConfigFileParams, TestMode}; - -use crate::cpu_test::common::{CpuTest, TestState}; - -use colored::*; -use flate2::read::GzDecoder; -use serde::{Deserialize, Serialize}; - pub fn run_processtests(config: ConfigFileParams) { let mut test_path = "./tests".to_string(); if let Some(test_dir) = &config.tests.test_dir { @@ -222,19 +233,28 @@ fn process_tests( #[cfg(feature = "cpu_validator")] use marty_core::cpu_validator::ValidatorMode; - let mut cpu = Cpu::new( - CpuType::Intel8088, - config.machine.cpu.trace_mode.unwrap_or_default(), - TraceLogger::None, - #[cfg(feature = "cpu_validator")] - ValidatorType::None, - #[cfg(feature = "cpu_validator")] - TraceLogger::None, - #[cfg(feature = "cpu_validator")] - ValidatorMode::Instruction, - #[cfg(feature = "cpu_validator")] - config.validator.baud_rate.unwrap_or(1_000_000), - ); + let mut cpu; + #[cfg(feature = "cpu_validator")] + { + cpu = match CpuBuilder::new() + .with_cpu_type(CpuType::Intel8088) + .with_cpu_subtype(CpuSubType::Intel8088) + .with_validator_type(ValidatorType::None) + .with_validator_mode(ValidatorMode::Instruction) + .with_validator_baud(config.validator.baud_rate.unwrap_or(1_000_000)) + .build() + { + Ok(cpu) => cpu, + Err(e) => { + log::error!("Failed to build CPU: {}", e); + std::process::exit(1); + } + } + }; + #[cfg(not(feature = "cpu_validator"))] + { + panic!("Validator feature not enabled!") + }; // We should have a vector of tests now. let total_tests = tests.len(); @@ -264,11 +284,11 @@ fn process_tests( } // Decode this instruction - let instruction_address = Cpu::calc_linear_address(cpu.get_register16(Register16::CS), cpu.ip()); + let instruction_address = cpu_common::calc_linear_address(cpu.get_register16(Register16::CS), cpu.get_ip()); cpu.bus_mut().seek(instruction_address as usize); - let mut i = match Cpu::decode(cpu.bus_mut(), true) { + let mut i = match cpu.get_type().decode(cpu.bus_mut(), true) { Ok(i) => i, Err(_) => { _ = writeln!(log, "Instruction decode error!"); diff --git a/frontends/martypc_desktop_wgpu/src/cpu_test/run_tests.rs b/frontends/martypc_desktop_wgpu/src/cpu_test/run_tests.rs index df100f33..31187be0 100644 --- a/frontends/martypc_desktop_wgpu/src/cpu_test/run_tests.rs +++ b/frontends/martypc_desktop_wgpu/src/cpu_test/run_tests.rs @@ -28,18 +28,6 @@ */ -use crate::cpu_test::common::{ - clean_cycle_states, - is_prefix_in_vec, - opcode_extension_from_path, - opcode_from_path, - print_cycle_diff, - print_summary, - read_tests_from_file, - validate_cycles, - validate_memory, - validate_registers, -}; use std::{ collections::{HashMap, LinkedList}, fs::{copy, create_dir, read_dir, File}, @@ -50,29 +38,44 @@ use std::{ time::{Duration, Instant}, }; +use colored::*; use config_toml_bpaf::{ConfigFileParams, TestMode}; - -use marty_core::{ - bytequeue::ByteQueue, - cpu_808x::{mnemonic::Mnemonic, Cpu, *}, - cpu_common, - cpu_common::{CpuOption, CpuType}, - cpu_validator::ValidatorType, - tracelogger::TraceLogger, -}; +use flate2::read::GzDecoder; use crate::{ - cpu_test::common::{CpuTest, FailType, Metadata, TestFailItem, TestResult}, + cpu_test::{ + common::{ + clean_cycle_states, + is_prefix_in_vec, + opcode_extension_from_path, + opcode_from_path, + print_cycle_diff, + print_summary, + read_tests_from_file, + validate_cycles, + validate_memory, + validate_registers, + CpuTest, + FailType, + Metadata, + TestFailItem, + TestFileLoad, + TestResult, + TestResultSummary, + }, + run_tests::cpu_common::CpuAddress, + }, trace_error, trace_print, }; -use crate::cpu_test::common::{TestFileLoad, TestResultSummary}; -use colored::*; -use flate2::read::GzDecoder; use marty_core::{ - cpu_common::{builder::CpuBuilder, CpuSubType, Register16}, - cpu_validator::ValidatorMode, + bytequeue::ByteQueue, + cpu_808x::{Cpu, *}, + cpu_common, + cpu_common::{builder::CpuBuilder, CpuOption, CpuSubType, CpuType, Mnemonic, Register16}, + cpu_validator::{ValidatorMode, ValidatorType}, + tracelogger::TraceLogger, }; pub fn run_runtests(config: ConfigFileParams) { @@ -325,7 +328,7 @@ fn run_tests( #[cfg(feature = "cpu_validator")] { cpu = match CpuBuilder::new() - .with_cpu_type(CpuType::Intel808x) + .with_cpu_type(CpuType::Intel8088) .with_cpu_subtype(CpuSubType::Intel8088) .with_trace_mode(trace_mode) .with_trace_logger(trace_logger) diff --git a/frontends/martypc_desktop_wgpu/src/event_loop/egui_update.rs b/frontends/martypc_desktop_wgpu/src/event_loop/egui_update.rs index c4043d8d..3ea68237 100644 --- a/frontends/martypc_desktop_wgpu/src/event_loop/egui_update.rs +++ b/frontends/martypc_desktop_wgpu/src/event_loop/egui_update.rs @@ -33,7 +33,7 @@ use crate::{event_loop::egui_events::handle_egui_event, Emulator}; use display_manager_wgpu::DisplayManager; use marty_core::{ bytequeue::ByteQueue, - cpu_808x::{Cpu, CpuAddress}, + cpu_808x::Cpu, cpu_common, cpu_common::CpuOption, machine, @@ -43,7 +43,7 @@ use marty_core::{ use marty_egui::GuiWindow; use frontend_common::timestep_manager::TimestepManager; -use marty_core::cpu_common::TraceMode; +use marty_core::cpu_common::{CpuAddress, TraceMode}; use winit::event_loop::EventLoopWindowTarget; pub fn update_egui(emu: &mut Emulator, tm: &TimestepManager, elwt: &EventLoopWindowTarget<()>) { diff --git a/frontends/martypc_desktop_wgpu/src/run_fuzzer.rs b/frontends/martypc_desktop_wgpu/src/run_fuzzer.rs index 0980dc40..ac62794f 100644 --- a/frontends/martypc_desktop_wgpu/src/run_fuzzer.rs +++ b/frontends/martypc_desktop_wgpu/src/run_fuzzer.rs @@ -28,6 +28,7 @@ Requires CPU validator feature. */ +use marty_core::cpu_common; use std::{ cell::RefCell, fs::File, @@ -40,8 +41,20 @@ use frontend_common::{floppy_manager::FloppyManager, rom_manager::RomManager}; use marty_core::{ bytequeue::ByteQueue, - cpu_808x::{mnemonic::Mnemonic, Cpu, *}, - cpu_common::{CpuOption, CpuType, TraceMode}, + cpu_common::{ + builder::CpuBuilder, + Cpu, + CpuAddress, + CpuOption, + CpuSubType, + CpuType, + Mnemonic, + OperandType, + Register16, + Register8, + TraceMode, + }, + cpu_validator::ValidatorType, devices::pic::Pic, tracelogger::TraceLogger, }; @@ -84,19 +97,31 @@ pub fn run_fuzzer(config: &ConfigFileParams) { #[cfg(feature = "cpu_validator")] use marty_core::cpu_validator::ValidatorMode; - let mut cpu = Cpu::new( - CpuType::Intel8088, - trace_mode, - cpu_trace, - #[cfg(feature = "cpu_validator")] - config.validator.vtype.unwrap(), - #[cfg(feature = "cpu_validator")] - validator_trace, - #[cfg(feature = "cpu_validator")] - ValidatorMode::Instruction, - #[cfg(feature = "cpu_validator")] - config.validator.baud_rate.unwrap_or(1_000_000), - ); + let mut cpu; + #[cfg(feature = "cpu_validator")] + { + cpu = match CpuBuilder::new() + .with_cpu_type(CpuType::Intel8088) + .with_cpu_subtype(CpuSubType::Intel8088) + .with_trace_mode(trace_mode) + .with_trace_logger(cpu_trace) + .with_validator_type(ValidatorType::None) + .with_validator_mode(ValidatorMode::Instruction) + .with_validator_logger(validator_trace) + .with_validator_baud(config.validator.baud_rate.unwrap_or(1_000_000)) + .build() + { + Ok(cpu) => cpu, + Err(e) => { + log::error!("Failed to build CPU: {}", e); + std::process::exit(1); + } + } + }; + #[cfg(not(feature = "cpu_validator"))] + { + panic!("Validator feature not enabled!") + }; cpu.randomize_seed(1234); cpu.randomize_mem(); @@ -109,12 +134,12 @@ pub fn run_fuzzer(config: &ConfigFileParams) { test_num += 1; cpu.randomize_regs(); - if cpu.ip() > 0xFFF0 { + if cpu.get_ip() > 0xFFF0 { // Avoid IP wrapping issues for now continue; } - if Cpu::calc_linear_address(cpu.get_register16(Register16::CS), cpu.ip()) > 0xFFFF0 { + if cpu_common::calc_linear_address(cpu.get_register16(Register16::CS), cpu.get_ip()) > 0xFFFF0 { // Avoid address space wrapping continue; } @@ -259,12 +284,12 @@ pub fn run_fuzzer(config: &ConfigFileParams) { //cpu.random_grp_instruction(0xFF, &[6, 7]); // PUSH & POP // Decode this instruction - let instruction_address = Cpu::calc_linear_address(cpu.get_register16(Register16::CS), cpu.ip()); + let instruction_address = cpu_common::calc_linear_address(cpu.get_register16(Register16::CS), cpu.get_ip()); cpu.bus_mut().seek(instruction_address as usize); let (opcode, _cost) = cpu.bus_mut().read_u8(instruction_address as usize, 0).expect("mem err"); - let mut i = match Cpu::decode(cpu.bus_mut(), true) { + let mut i = match cpu.get_type().decode(cpu.bus_mut(), true) { Ok(i) => i, Err(_) => { log::error!("Instruction decode error, skipping..."); @@ -362,10 +387,14 @@ pub fn run_fuzzer(config: &ConfigFileParams) { ); // Set terminating address for CPU validator. - let end_address = - Cpu::calc_linear_address(cpu.get_register16(Register16::CS), cpu.ip().wrapping_add(i.size as u16)); + // Set terminating address for CPU validator. + let end_address = cpu_common::calc_linear_address( + cpu.get_register16(Register16::CS), + cpu.get_ip().wrapping_add(i.size as u16), + ); - cpu.set_end_address(end_address as usize); + //log::debug!("Setting end address: {:05X}", end_address); + cpu.set_end_address(CpuAddress::Flat(end_address)); log::trace!("Setting end address: {:05X}", end_address); // We loop here to handle REP string instructions, which are broken up into 1 effective instruction diff --git a/lib/frontend/marty_egui/src/windows/cpu_control.rs b/lib/frontend/marty_egui/src/windows/cpu_control.rs index 3618a0b7..06121813 100644 --- a/lib/frontend/marty_egui/src/windows/cpu_control.rs +++ b/lib/frontend/marty_egui/src/windows/cpu_control.rs @@ -33,7 +33,7 @@ use crate::*; use marty_core::{ breakpoints::StopWatchData, - cpu_808x::CpuAddress, + cpu_common::CpuAddress, machine::{ExecutionControl, ExecutionOperation, ExecutionState}, }; use std::{cell::RefCell, collections::HashMap, rc::Rc}; From 4e79078fae87d55a9cf400654aa191c5f953c7b5 Mon Sep 17 00:00:00 2001 From: dbalsom Date: Mon, 6 May 2024 09:36:49 -0400 Subject: [PATCH 03/11] add REPC prefixes to V20 fuzzer --- core/src/cpu_vx0/fuzzer.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/core/src/cpu_vx0/fuzzer.rs b/core/src/cpu_vx0/fuzzer.rs index 5fc389d9..0ff03def 100644 --- a/core/src/cpu_vx0/fuzzer.rs +++ b/core/src/cpu_vx0/fuzzer.rs @@ -122,10 +122,16 @@ impl NecVx0 { 0xA4..=0xA7 | 0xAA..=0xAF => { // String ops match do_rep_prefix { - 0..=64 => { + 0..=32 => { + instr.push_front(0x64); // REPNC + } + 33..=64 => { + instr.push_front(0x65); // REPC + } + 64..=96 => { instr.push_front(0xF2); // REPNZ } - 65..=128 => { + 97..=128 => { instr.push_front(0xF3); // REPZ } _ => {} @@ -197,6 +203,13 @@ impl NecVx0 { // Filter out invalid forms of some instructions that cannot // reasonably be validated. match opcode { + // BOUND + 0x62 | 0x63 => { + if modrm_byte & 0xC0 == 0xC0 { + // Reg form, invalid. + continue; + } + } // LEA 0x8D => { if modrm_byte & 0xC0 == 0xC0 { From 14c81743c0db1a0125aba637db83f8e05da6839f Mon Sep 17 00:00:00 2001 From: dbalsom Date: Mon, 6 May 2024 09:38:06 -0400 Subject: [PATCH 04/11] split validator mem_ops and fetch_ops to tolerate fetch differences --- core/src/arduino8088_validator/mod.rs | 27 ++-- core/src/arduino8088_validator/remote_cpu.rs | 160 +++++++++++-------- 2 files changed, 105 insertions(+), 82 deletions(-) diff --git a/core/src/arduino8088_validator/mod.rs b/core/src/arduino8088_validator/mod.rs index 501f1651..a960548c 100644 --- a/core/src/arduino8088_validator/mod.rs +++ b/core/src/arduino8088_validator/mod.rs @@ -108,9 +108,9 @@ pub struct InstructionContext { regs: Vec, - emu_prefetch: Vec, + emu_fetches: Vec, emu_ops: Vec, - cpu_prefetch: Vec, + cpu_fetches: Vec, cpu_ops: Vec, mem_op_n: usize, @@ -131,9 +131,9 @@ impl InstructionContext { regs: vec![VRegisters::default(); 2], - emu_prefetch: Vec::new(), + emu_fetches: Vec::new(), emu_ops: Vec::new(), - cpu_prefetch: Vec::new(), + cpu_fetches: Vec::new(), cpu_ops: Vec::new(), mem_op_n: 0, cpu_states: Vec::new(), @@ -616,9 +616,9 @@ impl CpuValidator for ArduinoValidator { fn reset_instruction(&mut self) { self.current_instr.emu_ops.clear(); - self.current_instr.emu_prefetch.clear(); + self.current_instr.emu_fetches.clear(); self.current_instr.cpu_ops.clear(); - self.current_instr.cpu_prefetch.clear(); + self.current_instr.cpu_fetches.clear(); } fn begin_instruction(&mut self, regs: &VRegisters, end_instr: usize, end_program: usize) { @@ -763,17 +763,20 @@ impl CpuValidator for ArduinoValidator { trace_debug!( self, - "{}: {} {:02X?} @ [{:04X}:{:04X}] Memops: {} Start: {:05X} End: {:05X}", + "{}: {} {:02X?} @ [{:04X}:{:04X}] Memops: {} Fetches: {} Start: {:05X} End: {:05X}", discard_or_validate, name, self.current_instr.instr, self.current_instr.regs[0].cs, self.current_instr.regs[0].ip, self.current_instr.emu_ops.len(), + self.current_instr.emu_fetches.len(), ip_addr, self.current_instr.instr_end ); + trace_debug!(self, "\n{}", &RemoteCpu::get_reg_str(&self.current_instr.regs[0])); + if self.current_instr.discard { return Ok(ValidatorResult::Ok); } @@ -786,9 +789,9 @@ impl CpuValidator for ArduinoValidator { instr_addr, self.do_cycle_trace, peek_fetch, - &mut self.current_instr.emu_prefetch, + &mut self.current_instr.emu_fetches, &mut self.current_instr.emu_ops, - &mut self.current_instr.cpu_prefetch, + &mut self.current_instr.cpu_fetches, &mut self.current_instr.cpu_ops, &mut self.trace_logger, ) { @@ -826,9 +829,9 @@ impl CpuValidator for ArduinoValidator { } else if !self.validate_mem_ops(discard, flags) { trace_error!(self, "Memory validation failure. EMU:"); - RemoteCpu::print_regs(&self.current_instr.regs[1]); + trace_error!(self, "\n{}", &RemoteCpu::get_reg_str(&self.current_instr.regs[1])); trace_error!(self, "CPU:"); - RemoteCpu::print_regs(®s); + trace_error!(self, "\n{}", &RemoteCpu::get_reg_str(®s)); self.print_cycle_diff(&cpu_states, &emu_states); self.trace_logger.flush(); @@ -922,7 +925,7 @@ impl CpuValidator for ArduinoValidator { match (bus_type, read_type) { (BusType::Mem, ReadType::Code) => { - self.current_instr.emu_ops.push(BusOp { + self.current_instr.emu_fetches.push(BusOp { op_type: BusOpType::CodeRead, addr, data, diff --git a/core/src/arduino8088_validator/remote_cpu.rs b/core/src/arduino8088_validator/remote_cpu.rs index 82ab43b6..b480cf17 100644 --- a/core/src/arduino8088_validator/remote_cpu.rs +++ b/core/src/arduino8088_validator/remote_cpu.rs @@ -143,11 +143,11 @@ pub struct RemoteCpu { // Validator stuff busop_n: usize, + fetchop_n: usize, owe_busop: bool, fetch_rollover: bool, instruction_ended: bool, program_ended: bool, - prefetch_n: usize, v_pc: usize, } @@ -195,11 +195,11 @@ impl RemoteCpu { cycle_states: Vec::new(), busop_n: 0, + fetchop_n: 0, owe_busop: false, fetch_rollover: false, instruction_ended: false, program_ended: false, - prefetch_n: 0, v_pc: 0, } } @@ -308,7 +308,14 @@ impl RemoteCpu { } } - pub fn handle_bus_read(&mut self, emu_mem_ops: &Vec, cpu_mem_ops: &mut Vec, log: &mut TraceLogger) { + pub fn handle_bus_read( + &mut self, + emu_mem_ops: &Vec, + emu_fetch_ops: &Vec, + cpu_mem_ops: &mut Vec, + cpu_fetch_ops: &mut Vec, + log: &mut TraceLogger, + ) { if (self.command_status & COMMAND_MRDC_BIT) == 0 { // MRDC is active-low. CPU is reading from bus. @@ -316,15 +323,15 @@ impl RemoteCpu { // CPU is reading code. //log::debug!("bus_state: {:?} bus ops len: {}", self.bus_state, emu_mem_ops.len()); - if self.busop_n < emu_mem_ops.len() { + if self.fetchop_n < emu_fetch_ops.len() { // Bus type must match //assert!(emu_mem_ops[self.busop_n].op_type == BusOpType::CodeRead); - if (emu_mem_ops[self.busop_n].addr as usize) == self.instr_end_addr { + if (emu_fetch_ops[self.fetchop_n].addr as usize) == self.instr_end_addr { self.instruction_ended = true; } - if (emu_mem_ops[self.busop_n].addr as usize) == self.program_end_addr { + if (emu_fetch_ops[self.fetchop_n].addr as usize) == self.program_end_addr { self.program_ended = true; } @@ -342,11 +349,11 @@ impl RemoteCpu { // Feed emulator byte to CPU depending on validator mode. match self.mode { ValidatorMode::Cycle => { - self.data_bus = emu_mem_ops[self.busop_n].data; + self.data_bus = emu_fetch_ops[self.fetchop_n].data; self.data_type = QueueDataType::Program; } ValidatorMode::Instruction => { - if emu_mem_ops[self.busop_n].addr as usize >= self.instr_end_addr { + if emu_fetch_ops[self.fetchop_n].addr as usize >= self.instr_end_addr { self.data_type = QueueDataType::Finalize; self.data_bus = OPCODE_NOP; } @@ -354,28 +361,28 @@ impl RemoteCpu { trace!( log, "accepting fetch: {:05X} < {:05X}", - emu_mem_ops[self.busop_n].addr, + emu_fetch_ops[self.fetchop_n].addr, self.instr_end_addr ); - self.data_bus = emu_mem_ops[self.busop_n].data; + self.data_bus = emu_fetch_ops[self.fetchop_n].data; self.data_type = QueueDataType::Program; } } } - // Add emu op to CPU BusOp list - cpu_mem_ops.push(emu_mem_ops[self.busop_n].clone()); + // Add emu op to CPU FetchOp list + cpu_fetch_ops.push(emu_fetch_ops[self.fetchop_n].clone()); self.v_pc += 1; - if emu_mem_ops[self.busop_n].addr != self.address_latch { + if emu_fetch_ops[self.fetchop_n].addr != self.address_latch { trace!(log, "CPU fetch address != EMU fetch address"); } trace!( log, "CPU fetch: [{:05X}][{:05X}] -> 0x{:02X} cycle: {}", self.address_latch, - emu_mem_ops[self.busop_n].addr, + emu_fetch_ops[self.fetchop_n].addr, self.data_bus, self.cycle_num ); @@ -383,13 +390,14 @@ impl RemoteCpu { self.cpu_client .write_data_bus(self.data_bus) .expect("Failed to write data bus."); - self.busop_n += 1; + self.fetchop_n += 1; } else { // We have reached the end of the current instruction (fetched from instr_end_addr) ... - // flag the byte in the queue so we know to end this instruction when it is read out from the queue. + // flag the byte in the queue so that we know to end this instruction when + // it is read out from the queue. - if emu_mem_ops[self.busop_n].addr != self.address_latch { + if emu_fetch_ops[self.fetchop_n].addr != self.address_latch { trace!(log, "CPU fetch address != EMU fetch address"); } @@ -397,8 +405,8 @@ impl RemoteCpu { log, "CPU fetch next: [{:05X}][{:05X}] -> 0x{:02X} cycle: {}", self.address_latch, - emu_mem_ops[self.busop_n].addr, - emu_mem_ops[self.busop_n].data, + emu_fetch_ops[self.fetchop_n].addr, + emu_fetch_ops[self.fetchop_n].data, self.cycle_num ); /* @@ -410,7 +418,7 @@ impl RemoteCpu { */ // Fetch is past end address, send terminating NOP. - cpu_mem_ops.push(emu_mem_ops[self.busop_n].clone()); + cpu_fetch_ops.push(emu_fetch_ops[self.fetchop_n].clone()); // If we've reached the program end address, set the finalize flag on the queue byte so that // program state can be moved to Finalize and registers read out for comparison. @@ -423,23 +431,21 @@ impl RemoteCpu { self.data_bus = OPCODE_NOP; } } + else if self.program_ended { + self.instruction_ended = true; + self.data_type = QueueDataType::Finalize; + self.data_bus = OPCODE_NOP; + } else { - if self.program_ended { - self.instruction_ended = true; - self.data_type = QueueDataType::Finalize; - self.data_bus = OPCODE_NOP; - } - else { - //log::debug!("Setting data type to EndInstruction: data: {:02X}", emu_mem_ops[self.busop_n].data); - self.data_type = QueueDataType::EndInstruction; - self.data_bus = emu_mem_ops[self.busop_n].data; - } + //log::debug!("Setting data type to EndInstruction: data: {:02X}", emu_mem_ops[self.busop_n].data); + self.data_type = QueueDataType::EndInstruction; + self.data_bus = emu_mem_ops[self.busop_n].data; } self.cpu_client .write_data_bus(self.data_bus) .expect("Failed to write data bus."); - self.busop_n += 1; + self.fetchop_n += 1; } } else { @@ -456,35 +462,42 @@ impl RemoteCpu { trace!(log, "Can't rollover fetch twice!"); self.error = Some(RemoteCpuError::CannotOweMultipleOps); } - else { - if self.busop_n == emu_mem_ops.len() { - match self.mode { - ValidatorMode::Cycle => { - if ((self.address_latch as usize) == self.program_end_addr) { - self.data_type = QueueDataType::EndInstruction; - self.data_bus = OPCODE_NOP; - } - else { - trace!(log, "Bus op underflow on terminating fetch. Substituting fetch peek."); - // Substitute instruction byte for fetch op. - self.data_bus = (self.peek_fetch & 0xFF) as u8; - self.data_type = QueueDataType::Program; - self.fetch_rollover = true; - } - } - ValidatorMode::Instruction => { + else if self.busop_n == emu_mem_ops.len() { + match self.mode { + ValidatorMode::Cycle => { + if (self.address_latch as usize) == self.program_end_addr { self.data_type = QueueDataType::EndInstruction; self.data_bus = OPCODE_NOP; } + else { + trace!(log, "Fetch op underflow on terminating fetch. Substituting fetch peek."); + // Substitute instruction byte for fetch op. + self.data_bus = (self.peek_fetch & 0xFF) as u8; + self.data_type = QueueDataType::Program; + self.fetch_rollover = true; + } + } + ValidatorMode::Instruction => { + self.data_type = QueueDataType::EndInstruction; + self.data_bus = OPCODE_NOP; } - - self.cpu_client - .write_data_bus(self.data_bus) - .expect("Failed to write data bus."); } - else { - trace!(log, "Bus op underflow past terminating fetch."); - self.error = Some(RemoteCpuError::CannotOweMultipleOps); + + self.cpu_client + .write_data_bus(self.data_bus) + .expect("Failed to write data bus."); + } + else { + match self.mode { + ValidatorMode::Instruction => { + trace!(log, "Fetch op underflow. Substituting NOP"); + self.data_type = QueueDataType::EndInstruction; + self.data_bus = OPCODE_NOP; + } + ValidatorMode::Cycle => { + trace!(log, "Fetch op underflow in cycle mode. Cannot continue"); + self.error = Some(RemoteCpuError::CannotOweMultipleOps); + } } } } @@ -508,6 +521,7 @@ impl RemoteCpu { } else if self.bus_state == BusState::MEMR { // CPU is reading data from memory. + if self.busop_n < emu_mem_ops.len() { //assert!(emu_mem_ops[self.busop_n].op_type == BusOpType::MemRead); @@ -517,7 +531,7 @@ impl RemoteCpu { cpu_mem_ops.push(emu_mem_ops[self.busop_n].clone()); self.busop_n += 1; - trace!(log, "CPU read: {:02X}", self.data_bus); + trace!(log, "Bus OP {:02}: CPU read: {:02X}", self.busop_n, self.data_bus); self.cpu_client .write_data_bus(self.data_bus) .expect("Failed to write data bus."); @@ -539,7 +553,7 @@ impl RemoteCpu { cpu_mem_ops.push(emu_mem_ops[self.busop_n].clone()); self.busop_n += 1; - trace!(log, "CPU IN: {:02X}", self.data_bus); + trace!(log, "Bus OP {:02}: CPU IN: {:02X}", self.busop_n, self.data_bus); self.cpu_client .write_data_bus(self.data_bus) .expect("Failed to write data bus."); @@ -556,7 +570,7 @@ impl RemoteCpu { pub fn handle_bus_write(&mut self, emu_mem_ops: &Vec, cpu_mem_ops: &mut Vec, log: &mut TraceLogger) { // MWTC status is active-low. if ((self.command_status & COMMAND_AMWC_BIT) == 0) || ((self.command_status & COMMAND_MWTC_BIT) == 0) { - // CPU is writing to bus. MWTC is only active on t3 so we don't need an additional check. + // CPU is writing to bus. MWTC is only active on t3, so we don't need an additional check. if self.busop_n < emu_mem_ops.len() { //assert!(emu_mem_ops[self.busop_n].op_type == BusOpType::MemWrite); @@ -564,7 +578,13 @@ impl RemoteCpu { // Read byte from CPU self.data_bus = self.cpu_client.read_data_bus().expect("Failed to read data bus."); - trace!(log, "CPU write: [{:05X}] <- {:02X}", self.address_latch, self.data_bus); + trace!( + log, + "Bus OP {:02}: CPU write: [{:05X}] <- {:02X}", + self.busop_n, + self.address_latch, + self.data_bus + ); // Add write op to CPU BusOp list cpu_mem_ops.push(BusOp { @@ -576,7 +596,7 @@ impl RemoteCpu { self.busop_n += 1; } else { - trace!(log, "Bus op underflow on write"); + trace!(log, "Bus op underflow on write."); self.error = Some(RemoteCpuError::BusOpUnderflow); } } @@ -610,9 +630,9 @@ impl RemoteCpu { pub fn cycle( &mut self, instr: &[u8], - emu_prefetch: &Vec, + emu_fetch_ops: &Vec, emu_mem_ops: &Vec, - _cpu_prefetch: &mut Vec, + cpu_fetch_ops: &mut Vec, cpu_mem_ops: &mut Vec, log: &mut TraceLogger, ) -> Result { @@ -664,7 +684,7 @@ impl RemoteCpu { self.mcycle_state = get_bus_state!(self.status); } BusCycle::T2 => { - self.handle_bus_read(emu_mem_ops, cpu_mem_ops, log); + self.handle_bus_read(emu_mem_ops, emu_fetch_ops, cpu_mem_ops, cpu_fetch_ops, log); } BusCycle::T3 => { // TODO: Handle wait states @@ -885,9 +905,9 @@ impl RemoteCpu { instr_addr: u32, cycle_trace: bool, peek_fetch: u16, - emu_prefetch: &Vec, + emu_fetch_ops: &Vec, emu_mem_ops: &Vec, - cpu_prefetch: &mut Vec, + cpu_fetch_ops: &mut Vec, cpu_mem_ops: &mut Vec, log: &mut TraceLogger, ) -> Result<(Vec, bool), ValidatorError> { @@ -897,7 +917,7 @@ impl RemoteCpu { self.instr_addr = instr_addr; self.peek_fetch = peek_fetch; self.busop_n = 0; - self.prefetch_n = 0; + self.fetchop_n = 0; self.queue_first_fetch = false; self.rni = false; self.v_pc = 0; @@ -924,10 +944,10 @@ impl RemoteCpu { self.just_reset = false; } - // Discard first fetch if we are rolling over an missed terminating fetch from the previous instruction. - if self.fetch_rollover && (emu_mem_ops.len() >= 1) && (emu_mem_ops[0].op_type == BusOpType::CodeRead) { + // Discard first fetch if we are rolling over a missed terminating fetch from the previous instruction. + if self.fetch_rollover && (emu_fetch_ops.len() >= 1) { trace!(log, "Discarding fetch from previous instruction."); - self.busop_n += 1; + self.fetchop_n += 1; self.discard_front = true; self.fetch_rollover = false; } @@ -954,7 +974,7 @@ impl RemoteCpu { } while !self.end_instruction { - let mut cycle_state = match self.cycle(instr, emu_prefetch, emu_mem_ops, cpu_prefetch, cpu_mem_ops, log) { + let mut cycle_state = match self.cycle(instr, emu_fetch_ops, emu_mem_ops, cpu_fetch_ops, cpu_mem_ops, log) { Ok(cycle_state) => cycle_state, Err(e) => { trace!(log, "CPU error during step(): {}", e); From 9fa5b49f277809a6c883337263cdb5403e205d1e Mon Sep 17 00:00:00 2001 From: dbalsom Date: Mon, 6 May 2024 20:39:20 -0400 Subject: [PATCH 05/11] more v20 test work --- core/src/arduino8088_validator/mod.rs | 17 +- core/src/arduino8088_validator/remote_cpu.rs | 26 +- core/src/arduino8088_validator/udmask.rs | 385 +++++++++++++++++- core/src/bus.rs | 9 + core/src/cpu_808x/execute.rs | 1 + core/src/cpu_808x/mod.rs | 6 +- core/src/cpu_common/mod.rs | 1 + core/src/cpu_common/operands.rs | 1 + core/src/cpu_vx0/addressing.rs | 27 +- core/src/cpu_vx0/decode.rs | 205 ++++++---- core/src/cpu_vx0/execute.rs | 147 ++++++- core/src/cpu_vx0/mod.rs | 6 +- .../src/cpu_test/gen_tests.rs | 17 +- 13 files changed, 685 insertions(+), 163 deletions(-) diff --git a/core/src/arduino8088_validator/mod.rs b/core/src/arduino8088_validator/mod.rs index a960548c..972a8578 100644 --- a/core/src/arduino8088_validator/mod.rs +++ b/core/src/arduino8088_validator/mod.rs @@ -41,7 +41,7 @@ use crate::{ CPU_FLAG_TRAP, CPU_FLAG_ZERO, }, - cpu_common::QueueOp, + cpu_common::{CpuType, QueueOp}, cpu_validator::*, tracelogger::TraceLogger, }; @@ -152,7 +152,8 @@ pub fn difference>(a: T, b: T) - pub struct ArduinoValidator { //cpu_client: Option, mode: ValidatorMode, - cpu: RemoteCpu, + cpu: RemoteCpu, + cpu_type: CpuType, current_instr: InstructionContext, state: ValidatorState, @@ -195,7 +196,7 @@ pub struct ArduinoValidator { } impl ArduinoValidator { - pub fn new(trace_logger: TraceLogger, baud_rate: u32) -> Self { + pub fn new(cpu_type: CpuType, trace_logger: TraceLogger, baud_rate: u32) -> Self { // Trigger addr is address at which to start validation // if trigger_addr == V_INVALID_POINTER then validate let trigger_addr = V_INVALID_POINTER; @@ -210,6 +211,7 @@ impl ArduinoValidator { ArduinoValidator { mode: ValidatorMode::Cycle, cpu: RemoteCpu::new(cpu_client), + cpu_type, current_instr: InstructionContext::new(), state: ValidatorState::Setup, @@ -456,12 +458,17 @@ impl ArduinoValidator { if self.mask_flags { emu_flags_masked = ArduinoValidator::mask_undefined_flags( + self.cpu_type, self.current_instr.opcode, self.current_instr.modrm, self.current_instr.regs[1].flags, ); - cpu_flags_masked = - ArduinoValidator::mask_undefined_flags(self.current_instr.opcode, self.current_instr.modrm, regs.flags); + cpu_flags_masked = ArduinoValidator::mask_undefined_flags( + self.cpu_type, + self.current_instr.opcode, + self.current_instr.modrm, + regs.flags, + ); } if emu_flags_masked != cpu_flags_masked { diff --git a/core/src/arduino8088_validator/remote_cpu.rs b/core/src/arduino8088_validator/remote_cpu.rs index b480cf17..96d01c8e 100644 --- a/core/src/arduino8088_validator/remote_cpu.rs +++ b/core/src/arduino8088_validator/remote_cpu.rs @@ -308,6 +308,9 @@ impl RemoteCpu { } } + /// Handle a bus read operation, either code fetch, memory or IO read. + /// Code fetches are allowed to underflow in certain circumstances. + /// TODO: This should probably return a Result instead of setting an internal error condition. pub fn handle_bus_read( &mut self, emu_mem_ops: &Vec, @@ -318,15 +321,9 @@ impl RemoteCpu { ) { if (self.command_status & COMMAND_MRDC_BIT) == 0 { // MRDC is active-low. CPU is reading from bus. - if self.mcycle_state == BusState::CODE { - // CPU is reading code. - - //log::debug!("bus_state: {:?} bus ops len: {}", self.bus_state, emu_mem_ops.len()); + // CPU is fetching code. if self.fetchop_n < emu_fetch_ops.len() { - // Bus type must match - //assert!(emu_mem_ops[self.busop_n].op_type == BusOpType::CodeRead); - if (emu_fetch_ops[self.fetchop_n].addr as usize) == self.instr_end_addr { self.instruction_ended = true; } @@ -336,16 +333,6 @@ impl RemoteCpu { } if !self.instruction_ended { - /* - if instr[self.v_pc] != emu_mem_ops[self.busop_n].data { - log::error!( - "Emu fetch op doesn't match instruction vector byte: Fetch: {:02X} Instr: {:02X}", - emu_mem_ops[self.busop_n].data, - instr[self.v_pc] - ); - } - */ - // Feed emulator byte to CPU depending on validator mode. match self.mode { ValidatorMode::Cycle => { @@ -449,6 +436,11 @@ impl RemoteCpu { } } else { + if !self.instruction_ended { + trace!(log, "Fatal: fetch underflow within instruction!"); + self.error = Some(RemoteCpuError::CannotOweMultipleOps); + return; + } // We are allowed to miss a terminating fetch. // This is because while the emulator ends an instruction at the cycle in which the next diff --git a/core/src/arduino8088_validator/udmask.rs b/core/src/arduino8088_validator/udmask.rs index 78f8d856..028fe31c 100644 --- a/core/src/arduino8088_validator/udmask.rs +++ b/core/src/arduino8088_validator/udmask.rs @@ -24,7 +24,7 @@ */ #![allow(dead_code)] -use crate::arduino8088_validator::ArduinoValidator; +use crate::{arduino8088_validator::ArduinoValidator, cpu_common::CpuType}; pub const VFLAG_CARRY: u16 = 0x001; pub const VFLAG_PARITY: u16 = 0x004; @@ -45,7 +45,7 @@ pub struct FlagMask { } #[rustfmt::skip] -pub const FLAG_MASK_LOOKUP: [FlagMask; 256] = [ +pub const FLAG_MASK_LOOKUP_8088: [FlagMask; 256] = [ FlagMask { opcode: 0x00, group: 0, mask: 0 }, FlagMask { opcode: 0x01, group: 0, mask: 0 }, FlagMask { opcode: 0x02, group: 0, mask: 0 }, @@ -309,7 +309,271 @@ pub const FLAG_MASK_LOOKUP: [FlagMask; 256] = [ ]; #[rustfmt::skip] -pub const FLAG_MASK_GROUP_LOOKUP: [[FlagMask; 8]; 5] = [ +pub const FLAG_MASK_LOOKUP_V20: [FlagMask; 256] = [ + FlagMask { opcode: 0x00, group: 0, mask: 0 }, + FlagMask { opcode: 0x01, group: 0, mask: 0 }, + FlagMask { opcode: 0x02, group: 0, mask: 0 }, + FlagMask { opcode: 0x03, group: 0, mask: 0 }, + FlagMask { opcode: 0x04, group: 0, mask: 0 }, + FlagMask { opcode: 0x05, group: 0, mask: 0 }, + FlagMask { opcode: 0x06, group: 0, mask: 0 }, + FlagMask { opcode: 0x07, group: 0, mask: 0 }, + FlagMask { opcode: 0x08, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x09, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x0A, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x0B, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x0C, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x0D, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x0E, group: 0, mask: 0 }, + FlagMask { opcode: 0x0F, group: 0, mask: 0 }, + FlagMask { opcode: 0x10, group: 0, mask: 0 }, + FlagMask { opcode: 0x11, group: 0, mask: 0 }, + FlagMask { opcode: 0x12, group: 0, mask: 0 }, + FlagMask { opcode: 0x13, group: 0, mask: 0 }, + FlagMask { opcode: 0x14, group: 0, mask: 0 }, + FlagMask { opcode: 0x15, group: 0, mask: 0 }, + FlagMask { opcode: 0x16, group: 0, mask: 0 }, + FlagMask { opcode: 0x17, group: 0, mask: 0 }, + FlagMask { opcode: 0x18, group: 0, mask: 0 }, + FlagMask { opcode: 0x19, group: 0, mask: 0 }, + FlagMask { opcode: 0x1A, group: 0, mask: 0 }, + FlagMask { opcode: 0x1B, group: 0, mask: 0 }, + FlagMask { opcode: 0x1C, group: 0, mask: 0 }, + FlagMask { opcode: 0x1D, group: 0, mask: 0 }, + FlagMask { opcode: 0x1E, group: 0, mask: 0 }, + FlagMask { opcode: 0x1F, group: 0, mask: 0 }, + FlagMask { opcode: 0x20, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x21, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x22, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x23, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x24, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x25, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x26, group: 0, mask: 0 }, + //FlagMask { opcode: 0x27, group: 0, mask: VFLAG_OVERFLOW }, + FlagMask { opcode: 0x27, group: 0, mask: 0 }, // DAA - implemented overflow flag behavior + FlagMask { opcode: 0x28, group: 0, mask: 0 }, + FlagMask { opcode: 0x29, group: 0, mask: 0 }, + FlagMask { opcode: 0x2A, group: 0, mask: 0 }, + FlagMask { opcode: 0x2B, group: 0, mask: 0 }, + FlagMask { opcode: 0x2C, group: 0, mask: 0 }, + FlagMask { opcode: 0x2D, group: 0, mask: 0 }, + FlagMask { opcode: 0x2E, group: 0, mask: 0 }, + //FlagMask { opcode: 0x2F, group: 0, mask: VFLAG_OVERFLOW }, + FlagMask { opcode: 0x2F, group: 0, mask: 0 }, // DAS - implemented overflow flag behavior + FlagMask { opcode: 0x30, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x31, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x32, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x33, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x34, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x35, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x36, group: 0, mask: 0 }, + //FlagMask { opcode: 0x37, group: 0, mask: VFLAG_PARITY | VFLAG_ZERO | VFLAG_SIGN | VFLAG_OVERFLOW }, + FlagMask { opcode: 0x37, group: 0, mask: 0 }, // AAA - implemented flag behavior + FlagMask { opcode: 0x38, group: 0, mask: 0 }, + FlagMask { opcode: 0x39, group: 0, mask: 0 }, + FlagMask { opcode: 0x3A, group: 0, mask: 0 }, + FlagMask { opcode: 0x3B, group: 0, mask: 0 }, + FlagMask { opcode: 0x3C, group: 0, mask: 0 }, + FlagMask { opcode: 0x3D, group: 0, mask: 0 }, + FlagMask { opcode: 0x3E, group: 0, mask: 0 }, + //FlagMask { opcode: 0x3F, group: 0, mask: VFLAG_PARITY | VFLAG_ZERO | VFLAG_SIGN | VFLAG_OVERFLOW }, + FlagMask { opcode: 0x3F, group: 0, mask: 0 }, // AAS - implemented flag behavior + FlagMask { opcode: 0x40, group: 0, mask: 0 }, + FlagMask { opcode: 0x41, group: 0, mask: 0 }, + FlagMask { opcode: 0x42, group: 0, mask: 0 }, + FlagMask { opcode: 0x43, group: 0, mask: 0 }, + FlagMask { opcode: 0x44, group: 0, mask: 0 }, + FlagMask { opcode: 0x45, group: 0, mask: 0 }, + FlagMask { opcode: 0x46, group: 0, mask: 0 }, + FlagMask { opcode: 0x47, group: 0, mask: 0 }, + FlagMask { opcode: 0x48, group: 0, mask: 0 }, + FlagMask { opcode: 0x49, group: 0, mask: 0 }, + FlagMask { opcode: 0x4A, group: 0, mask: 0 }, + FlagMask { opcode: 0x4B, group: 0, mask: 0 }, + FlagMask { opcode: 0x4C, group: 0, mask: 0 }, + FlagMask { opcode: 0x4D, group: 0, mask: 0 }, + FlagMask { opcode: 0x4E, group: 0, mask: 0 }, + FlagMask { opcode: 0x4F, group: 0, mask: 0 }, + FlagMask { opcode: 0x50, group: 0, mask: 0 }, + FlagMask { opcode: 0x51, group: 0, mask: 0 }, + FlagMask { opcode: 0x52, group: 0, mask: 0 }, + FlagMask { opcode: 0x53, group: 0, mask: 0 }, + FlagMask { opcode: 0x54, group: 0, mask: 0 }, + FlagMask { opcode: 0x55, group: 0, mask: 0 }, + FlagMask { opcode: 0x56, group: 0, mask: 0 }, + FlagMask { opcode: 0x57, group: 0, mask: 0 }, + FlagMask { opcode: 0x58, group: 0, mask: 0 }, + FlagMask { opcode: 0x59, group: 0, mask: 0 }, + FlagMask { opcode: 0x5A, group: 0, mask: 0 }, + FlagMask { opcode: 0x5B, group: 0, mask: 0 }, + FlagMask { opcode: 0x5C, group: 0, mask: 0 }, + FlagMask { opcode: 0x5D, group: 0, mask: 0 }, + FlagMask { opcode: 0x5E, group: 0, mask: 0 }, + FlagMask { opcode: 0x5F, group: 0, mask: 0 }, + FlagMask { opcode: 0x60, group: 0, mask: 0 }, + FlagMask { opcode: 0x61, group: 0, mask: 0 }, + FlagMask { opcode: 0x62, group: 0, mask: 0 }, + FlagMask { opcode: 0x63, group: 0, mask: 0 }, + FlagMask { opcode: 0x64, group: 0, mask: 0 }, + FlagMask { opcode: 0x65, group: 0, mask: 0 }, + FlagMask { opcode: 0x66, group: 0, mask: 0 }, + FlagMask { opcode: 0x67, group: 0, mask: 0 }, + FlagMask { opcode: 0x68, group: 0, mask: 0 }, + FlagMask { opcode: 0x69, group: 0, mask: 0 }, + FlagMask { opcode: 0x6A, group: 0, mask: 0 }, + FlagMask { opcode: 0x6B, group: 0, mask: 0 }, + FlagMask { opcode: 0x6C, group: 0, mask: 0 }, + FlagMask { opcode: 0x6D, group: 0, mask: 0 }, + FlagMask { opcode: 0x6E, group: 0, mask: 0 }, + FlagMask { opcode: 0x6F, group: 0, mask: 0 }, + FlagMask { opcode: 0x70, group: 0, mask: 0 }, + FlagMask { opcode: 0x71, group: 0, mask: 0 }, + FlagMask { opcode: 0x72, group: 0, mask: 0 }, + FlagMask { opcode: 0x73, group: 0, mask: 0 }, + FlagMask { opcode: 0x74, group: 0, mask: 0 }, + FlagMask { opcode: 0x75, group: 0, mask: 0 }, + FlagMask { opcode: 0x76, group: 0, mask: 0 }, + FlagMask { opcode: 0x77, group: 0, mask: 0 }, + FlagMask { opcode: 0x78, group: 0, mask: 0 }, + FlagMask { opcode: 0x79, group: 0, mask: 0 }, + FlagMask { opcode: 0x7A, group: 0, mask: 0 }, + FlagMask { opcode: 0x7B, group: 0, mask: 0 }, + FlagMask { opcode: 0x7C, group: 0, mask: 0 }, + FlagMask { opcode: 0x7D, group: 0, mask: 0 }, + FlagMask { opcode: 0x7E, group: 0, mask: 0 }, + FlagMask { opcode: 0x7F, group: 0, mask: 0 }, + FlagMask { opcode: 0x80, group: 1, mask: 0 }, + FlagMask { opcode: 0x81, group: 1, mask: 0 }, + FlagMask { opcode: 0x82, group: 1, mask: 0 }, + FlagMask { opcode: 0x83, group: 1, mask: 0 }, + FlagMask { opcode: 0x84, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x85, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x86, group: 0, mask: 0 }, + FlagMask { opcode: 0x87, group: 0, mask: 0 }, + FlagMask { opcode: 0x88, group: 0, mask: 0 }, + FlagMask { opcode: 0x89, group: 0, mask: 0 }, + FlagMask { opcode: 0x8A, group: 0, mask: 0 }, + FlagMask { opcode: 0x8B, group: 0, mask: 0 }, + FlagMask { opcode: 0x8C, group: 0, mask: 0 }, + FlagMask { opcode: 0x8D, group: 0, mask: 0 }, + FlagMask { opcode: 0x8E, group: 0, mask: 0 }, + FlagMask { opcode: 0x8F, group: 0, mask: 0 }, + FlagMask { opcode: 0x90, group: 0, mask: 0 }, + FlagMask { opcode: 0x91, group: 0, mask: 0 }, + FlagMask { opcode: 0x92, group: 0, mask: 0 }, + FlagMask { opcode: 0x93, group: 0, mask: 0 }, + FlagMask { opcode: 0x94, group: 0, mask: 0 }, + FlagMask { opcode: 0x95, group: 0, mask: 0 }, + FlagMask { opcode: 0x96, group: 0, mask: 0 }, + FlagMask { opcode: 0x97, group: 0, mask: 0 }, + FlagMask { opcode: 0x98, group: 0, mask: 0 }, + FlagMask { opcode: 0x99, group: 0, mask: 0 }, + FlagMask { opcode: 0x9A, group: 0, mask: 0 }, + FlagMask { opcode: 0x9B, group: 0, mask: 0 }, + FlagMask { opcode: 0x9C, group: 0, mask: 0 }, + FlagMask { opcode: 0x9D, group: 0, mask: 0 }, + FlagMask { opcode: 0x9E, group: 0, mask: 0 }, + FlagMask { opcode: 0x9F, group: 0, mask: 0 }, + FlagMask { opcode: 0xA0, group: 0, mask: 0 }, + FlagMask { opcode: 0xA1, group: 0, mask: 0 }, + FlagMask { opcode: 0xA2, group: 0, mask: 0 }, + FlagMask { opcode: 0xA3, group: 0, mask: 0 }, + FlagMask { opcode: 0xA4, group: 0, mask: 0 }, + FlagMask { opcode: 0xA5, group: 0, mask: 0 }, + FlagMask { opcode: 0xA6, group: 0, mask: 0 }, + FlagMask { opcode: 0xA7, group: 0, mask: 0 }, + FlagMask { opcode: 0xA8, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0xA9, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0xAA, group: 0, mask: 0 }, + FlagMask { opcode: 0xAB, group: 0, mask: 0 }, + FlagMask { opcode: 0xAC, group: 0, mask: 0 }, + FlagMask { opcode: 0xAD, group: 0, mask: 0 }, + FlagMask { opcode: 0xAE, group: 0, mask: 0 }, + FlagMask { opcode: 0xAF, group: 0, mask: 0 }, + FlagMask { opcode: 0xB0, group: 0, mask: 0 }, + FlagMask { opcode: 0xB1, group: 0, mask: 0 }, + FlagMask { opcode: 0xB2, group: 0, mask: 0 }, + FlagMask { opcode: 0xB3, group: 0, mask: 0 }, + FlagMask { opcode: 0xB4, group: 0, mask: 0 }, + FlagMask { opcode: 0xB5, group: 0, mask: 0 }, + FlagMask { opcode: 0xB6, group: 0, mask: 0 }, + FlagMask { opcode: 0xB7, group: 0, mask: 0 }, + FlagMask { opcode: 0xB8, group: 0, mask: 0 }, + FlagMask { opcode: 0xB9, group: 0, mask: 0 }, + FlagMask { opcode: 0xBA, group: 0, mask: 0 }, + FlagMask { opcode: 0xBB, group: 0, mask: 0 }, + FlagMask { opcode: 0xBC, group: 0, mask: 0 }, + FlagMask { opcode: 0xBD, group: 0, mask: 0 }, + FlagMask { opcode: 0xBE, group: 0, mask: 0 }, + FlagMask { opcode: 0xBF, group: 0, mask: 0 }, + FlagMask { opcode: 0xC0, group: 2, mask: 0 }, + FlagMask { opcode: 0xC1, group: 2, mask: 0 }, + FlagMask { opcode: 0xC2, group: 0, mask: 0 }, + FlagMask { opcode: 0xC3, group: 0, mask: 0 }, + FlagMask { opcode: 0xC4, group: 0, mask: 0 }, + FlagMask { opcode: 0xC5, group: 0, mask: 0 }, + FlagMask { opcode: 0xC6, group: 0, mask: 0 }, + FlagMask { opcode: 0xC7, group: 0, mask: 0 }, + FlagMask { opcode: 0xC8, group: 0, mask: 0 }, + FlagMask { opcode: 0xC9, group: 0, mask: 0 }, + FlagMask { opcode: 0xCA, group: 0, mask: 0 }, + FlagMask { opcode: 0xCB, group: 0, mask: 0 }, + FlagMask { opcode: 0xCC, group: 0, mask: 0 }, + FlagMask { opcode: 0xCD, group: 0, mask: 0 }, + FlagMask { opcode: 0xCE, group: 0, mask: 0 }, + FlagMask { opcode: 0xCF, group: 0, mask: 0 }, + FlagMask { opcode: 0xD0, group: 3, mask: 0 }, + FlagMask { opcode: 0xD1, group: 3, mask: 0 }, + FlagMask { opcode: 0xD2, group: 4, mask: 0 }, + FlagMask { opcode: 0xD3, group: 4, mask: 0 }, + FlagMask { opcode: 0xD4, group: 0, mask: VFLAG_CARRY | VFLAG_AUXILIARY | VFLAG_OVERFLOW }, + FlagMask { opcode: 0xD5, group: 0, mask: VFLAG_CARRY | VFLAG_AUXILIARY | VFLAG_OVERFLOW }, + FlagMask { opcode: 0xD6, group: 0, mask: 0 }, + FlagMask { opcode: 0xD7, group: 0, mask: 0 }, + FlagMask { opcode: 0xD8, group: 0, mask: 0 }, + FlagMask { opcode: 0xD9, group: 0, mask: 0 }, + FlagMask { opcode: 0xDA, group: 0, mask: 0 }, + FlagMask { opcode: 0xDB, group: 0, mask: 0 }, + FlagMask { opcode: 0xDC, group: 0, mask: 0 }, + FlagMask { opcode: 0xDD, group: 0, mask: 0 }, + FlagMask { opcode: 0xDE, group: 0, mask: 0 }, + FlagMask { opcode: 0xDF, group: 0, mask: 0 }, + FlagMask { opcode: 0xE0, group: 0, mask: 0 }, + FlagMask { opcode: 0xE1, group: 0, mask: 0 }, + FlagMask { opcode: 0xE2, group: 0, mask: 0 }, + FlagMask { opcode: 0xE3, group: 0, mask: 0 }, + FlagMask { opcode: 0xE4, group: 0, mask: 0 }, + FlagMask { opcode: 0xE5, group: 0, mask: 0 }, + FlagMask { opcode: 0xE6, group: 0, mask: 0 }, + FlagMask { opcode: 0xE7, group: 0, mask: 0 }, + FlagMask { opcode: 0xE8, group: 0, mask: 0 }, + FlagMask { opcode: 0xE9, group: 0, mask: 0 }, + FlagMask { opcode: 0xEA, group: 0, mask: 0 }, + FlagMask { opcode: 0xEB, group: 0, mask: 0 }, + FlagMask { opcode: 0xEC, group: 0, mask: 0 }, + FlagMask { opcode: 0xED, group: 0, mask: 0 }, + FlagMask { opcode: 0xEE, group: 0, mask: 0 }, + FlagMask { opcode: 0xEF, group: 0, mask: 0 }, + FlagMask { opcode: 0xF0, group: 0, mask: 0 }, + FlagMask { opcode: 0xF1, group: 0, mask: 0 }, + FlagMask { opcode: 0xF2, group: 0, mask: 0 }, + FlagMask { opcode: 0xF3, group: 0, mask: 0 }, + FlagMask { opcode: 0xF4, group: 0, mask: 0 }, + FlagMask { opcode: 0xF5, group: 0, mask: 0 }, + FlagMask { opcode: 0xF6, group: 5, mask: 0 }, + FlagMask { opcode: 0xF7, group: 5, mask: 0 }, + FlagMask { opcode: 0xF8, group: 0, mask: 0 }, + FlagMask { opcode: 0xF9, group: 0, mask: 0 }, + FlagMask { opcode: 0xFA, group: 0, mask: 0 }, + FlagMask { opcode: 0xFB, group: 0, mask: 0 }, + FlagMask { opcode: 0xFC, group: 0, mask: 0 }, + FlagMask { opcode: 0xFD, group: 0, mask: 0 }, + FlagMask { opcode: 0xFE, group: 6, mask: 0 }, + FlagMask { opcode: 0xFF, group: 6, mask: 0 } +]; + +#[rustfmt::skip] +pub const FLAG_MASK_GROUP_LOOKUP_8088: [[FlagMask; 8]; 5] = [ // Group #1 0x80-0x83 [ FlagMask { opcode: 0x80, group: 0, mask: 0 }, @@ -367,26 +631,117 @@ pub const FLAG_MASK_GROUP_LOOKUP: [[FlagMask; 8]; 5] = [ ], ]; +#[rustfmt::skip] +pub const FLAG_MASK_GROUP_LOOKUP_V20: [[FlagMask; 8]; 6] = [ + // Group #1 0x80-0x83 + [ + FlagMask { opcode: 0x80, group: 0, mask: 0 }, + FlagMask { opcode: 0x80, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x80, group: 0, mask: 0 }, + FlagMask { opcode: 0x80, group: 0, mask: 0 }, + FlagMask { opcode: 0x80, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x80, group: 0, mask: 0 }, + FlagMask { opcode: 0x80, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0x80, group: 0, mask: 0 }, + ], + // Group #2 0xC0-0xC1 + [ + FlagMask { opcode: 0xC0, group: 0, mask: 0 }, + FlagMask { opcode: 0xC0, group: 0, mask: 0 }, + FlagMask { opcode: 0xC0, group: 0, mask: 0 }, + FlagMask { opcode: 0xC0, group: 0, mask: 0 }, + FlagMask { opcode: 0xC0, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0xC0, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0xC0, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0xC0, group: 0, mask: VFLAG_AUXILIARY }, + ], + [ + FlagMask { opcode: 0xD0, group: 0, mask: 0 }, + FlagMask { opcode: 0xD0, group: 0, mask: 0 }, + FlagMask { opcode: 0xD0, group: 0, mask: 0 }, + FlagMask { opcode: 0xD0, group: 0, mask: 0 }, + FlagMask { opcode: 0xD0, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0xD0, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0xD0, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0xD0, group: 0, mask: VFLAG_AUXILIARY }, + ], + // Group #4 0xD2-0xD3 + [ + FlagMask { opcode: 0xD2, group: 0, mask: VFLAG_OVERFLOW }, + FlagMask { opcode: 0xD2, group: 0, mask: VFLAG_OVERFLOW }, + FlagMask { opcode: 0xD2, group: 0, mask: VFLAG_OVERFLOW }, + FlagMask { opcode: 0xD2, group: 0, mask: VFLAG_OVERFLOW }, + FlagMask { opcode: 0xD2, group: 0, mask: VFLAG_CARRY | VFLAG_AUXILIARY | VFLAG_OVERFLOW }, + FlagMask { opcode: 0xD2, group: 0, mask: VFLAG_CARRY | VFLAG_AUXILIARY | VFLAG_OVERFLOW }, + FlagMask { opcode: 0xD2, group: 0, mask: VFLAG_CARRY | VFLAG_AUXILIARY | VFLAG_OVERFLOW }, + FlagMask { opcode: 0xD2, group: 0, mask: VFLAG_AUXILIARY | VFLAG_OVERFLOW }, + ], + // Group #5 0xF6-0xF7 + [ + FlagMask { opcode: 0xF6, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0xF6, group: 0, mask: VFLAG_AUXILIARY }, + FlagMask { opcode: 0xF6, group: 0, mask: 0 }, + FlagMask { opcode: 0xF6, group: 0, mask: 0 }, + FlagMask { opcode: 0xF6, group: 0, mask: VFLAG_PARITY | VFLAG_AUXILIARY | VFLAG_ZERO | VFLAG_SIGN }, + FlagMask { opcode: 0xF6, group: 0, mask: VFLAG_PARITY | VFLAG_AUXILIARY | VFLAG_ZERO | VFLAG_SIGN }, + FlagMask { opcode: 0xF6, group: 0, mask: VFLAG_CARRY | VFLAG_PARITY | VFLAG_AUXILIARY | VFLAG_ZERO | VFLAG_SIGN | VFLAG_OVERFLOW }, + FlagMask { opcode: 0xF6, group: 0, mask: VFLAG_CARRY | VFLAG_PARITY | VFLAG_AUXILIARY | VFLAG_ZERO | VFLAG_SIGN | VFLAG_OVERFLOW }, + ], + // Group #6 0xFE-0xFF + [ + FlagMask { opcode: 0xFE, group: 0, mask: 0 }, + FlagMask { opcode: 0xFE, group: 0, mask: 0 }, + FlagMask { opcode: 0xFE, group: 0, mask: 0 }, + FlagMask { opcode: 0xFE, group: 0, mask: 0 }, + FlagMask { opcode: 0xFE, group: 0, mask: 0 }, + FlagMask { opcode: 0xFE, group: 0, mask: 0 }, + FlagMask { opcode: 0xFE, group: 0, mask: 0 }, + FlagMask { opcode: 0xFE, group: 0, mask: 0 }, + ], +]; + impl ArduinoValidator { - pub fn mask_undefined_flags(opcode: u8, modrm: u8, flags: u16) -> u16 { + pub fn mask_undefined_flags(cputype: CpuType, opcode: u8, modrm: u8, flags: u16) -> u16 { let mut masked_flags = flags & IGNORE_MASK; // Ignore I, T and reserved flags - let grp = FLAG_MASK_LOOKUP[opcode as usize].group as usize; + match cputype { + CpuType::NecV20 => { + let grp = FLAG_MASK_LOOKUP_V20[opcode as usize].group as usize; - if grp == 0 { - // Not a group opcode, mask directly. - masked_flags &= !FLAG_MASK_LOOKUP[opcode as usize].mask; - } - else { - // Is group opcode, look up from group table. - let grp_op = ((modrm >> 3) & 0x07) as usize; - masked_flags &= !FLAG_MASK_GROUP_LOOKUP[grp - 1][grp_op].mask; + if grp == 0 { + // Not a group opcode, mask directly. + masked_flags &= !FLAG_MASK_LOOKUP_V20[opcode as usize].mask; + } + else { + // Is group opcode, look up from group table. + let grp_op = ((modrm >> 3) & 0x07) as usize; + masked_flags &= !FLAG_MASK_GROUP_LOOKUP_V20[grp - 1][grp_op].mask; + } + } + CpuType::Intel8088 | CpuType::Intel8086 => { + let grp = FLAG_MASK_LOOKUP_8088[opcode as usize].group as usize; + + if grp == 0 { + // Not a group opcode, mask directly. + masked_flags &= !FLAG_MASK_LOOKUP_8088[opcode as usize].mask; + } + else { + // Is group opcode, look up from group table. + let grp_op = ((modrm >> 3) & 0x07) as usize; + masked_flags &= !FLAG_MASK_GROUP_LOOKUP_8088[grp - 1][grp_op].mask; + } + } + _ => panic!("No groups defined for CpuType"), } masked_flags } - pub fn is_group_opcode(opcode: u8) -> bool { - FLAG_MASK_LOOKUP[opcode as usize].group != 0 + pub fn is_group_opcode(cputype: CpuType, opcode: u8) -> bool { + match cputype { + CpuType::NecV20 => FLAG_MASK_LOOKUP_V20[opcode as usize].group != 0, + CpuType::Intel8088 | CpuType::Intel8086 => FLAG_MASK_LOOKUP_V20[opcode as usize].group != 0, + _ => panic!("No groups defined for CpuType"), + } } } diff --git a/core/src/bus.rs b/core/src/bus.rs index bc446758..e474ff6a 100644 --- a/core/src/bus.rs +++ b/core/src/bus.rs @@ -988,6 +988,15 @@ impl BusInterface { Err(MemError::ReadOutOfBoundsError) } + pub fn peek_range(&self, address: usize, len: usize) -> Result<&[u8], MemError> { + if address + len < self.memory.len() { + Ok(&self.memory[address..address + len]) + } + else { + Err(MemError::ReadOutOfBoundsError) + } + } + pub fn peek_u8(&self, address: usize) -> Result { if address < self.memory.len() { if self.memory_mask[address] & MEM_MMIO_BIT == 0 { diff --git a/core/src/cpu_808x/execute.rs b/core/src/cpu_808x/execute.rs index 3ff6f86d..2cc2e157 100644 --- a/core/src/cpu_808x/execute.rs +++ b/core/src/cpu_808x/execute.rs @@ -2083,6 +2083,7 @@ impl Intel808x { match exception { CpuException::DivideError => ExecutionResult::ExceptionError(exception), CpuException::NoException => ExecutionResult::Okay, + _ => panic!("Invalid exception type!") } } } diff --git a/core/src/cpu_808x/mod.rs b/core/src/cpu_808x/mod.rs index c59231a5..68ac1590 100644 --- a/core/src/cpu_808x/mod.rs +++ b/core/src/cpu_808x/mod.rs @@ -842,7 +842,11 @@ impl Intel808x { { cpu.validator = match validator_type { #[cfg(feature = "arduino_validator")] - ValidatorType::Arduino8088 => Some(Box::new(ArduinoValidator::new(validator_trace, validator_baud))), + ValidatorType::Arduino8088 => Some(Box::new(ArduinoValidator::new( + cpu_type, + validator_trace, + validator_baud, + ))), _ => None, }; diff --git a/core/src/cpu_common/mod.rs b/core/src/cpu_common/mod.rs index 433e5076..6a6c3a38 100644 --- a/core/src/cpu_common/mod.rs +++ b/core/src/cpu_common/mod.rs @@ -79,6 +79,7 @@ pub enum ExecutionResult { pub enum CpuException { NoException, DivideError, + BoundsException, } #[derive(Copy, Clone, PartialEq)] diff --git a/core/src/cpu_common/operands.rs b/core/src/cpu_common/operands.rs index 34d848d3..ec002cc9 100644 --- a/core/src/cpu_common/operands.rs +++ b/core/src/cpu_common/operands.rs @@ -45,6 +45,7 @@ pub enum OperandType { Register16(Register16), AddressingMode(AddressingMode), FarAddress(u16, u16), + M16Pair(u16, u16), NoOperand, InvalidOperand, } diff --git a/core/src/cpu_vx0/addressing.rs b/core/src/cpu_vx0/addressing.rs index 28d4a532..bfe6f6d6 100644 --- a/core/src/cpu_vx0/addressing.rs +++ b/core/src/cpu_vx0/addressing.rs @@ -319,33 +319,18 @@ impl NecVx0 { } } - pub fn read_operand_farptr2( + pub fn read_operand_m16m16( &mut self, operand: OperandType, seg_override: Option, - ptr: FarPtr, flag: ReadWriteFlag, - ) -> Option { + ) -> Option<(u16, u16)> { match operand { OperandType::AddressingMode(mode) => { - let (segment, offset) = self.calc_effective_address(mode, seg_override); - - match ptr { - FarPtr::Offset => Some(self.biu_read_u16(segment, offset, flag)), - FarPtr::Segment => { - Some(self.biu_read_u16(segment, offset.wrapping_add(2), flag)) - } - } - } - OperandType::Register16(_) => { - // Illegal form of LES/LDS reg/reg uses the last calculated EA. - let segment_base_ds = self.i.segment_override.unwrap_or(Segment::DS); - match ptr { - FarPtr::Offset => Some(0), - FarPtr::Segment => { - Some(self.biu_read_u16(segment_base_ds, self.last_ea.wrapping_add(2), flag)) - } - } + let m1 = self.ea_opr; + let (segment, ea_offset) = self.calc_effective_address(mode, seg_override); + let m2 = self.biu_read_u16(segment, ea_offset.wrapping_add(2), flag); + Some((m1, m2)) } _ => None, } diff --git a/core/src/cpu_vx0/decode.rs b/core/src/cpu_vx0/decode.rs index b8d1ca13..32c03e48 100644 --- a/core/src/cpu_vx0/decode.rs +++ b/core/src/cpu_vx0/decode.rs @@ -69,6 +69,7 @@ pub enum OperandTemplate { FixedRegister16(Register16), //NearAddress, FarAddress, + M16Pair, } type Ot = OperandTemplate; @@ -142,7 +143,7 @@ macro_rules! inst { } #[rustfmt::skip] -pub const DECODE: [InstTemplate; 352] = [ +pub const DECODE: [InstTemplate; 368] = [ inst!( 0x00, 0, 0b0100101000000000, 0x008, ADD , ADD, Ot::ModRM8, Ot::Register8), inst!( 0x01, 0, 0b0100101000000000, 0x008, ADD , ADD, Ot::ModRM16, Ot::Register16), inst!( 0x02, 0, 0b0100101000000000, 0x008, ADD , ADD, Ot::Register8, Ot::ModRM8), @@ -241,16 +242,16 @@ pub const DECODE: [InstTemplate; 352] = [ inst!( 0x5F, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), inst!( 0x60, 0, 0b0000000000010000, 0x0e8, PUSHA, Ot::NoOperand, Ot::NoOperand), inst!( 0x61, 0, 0b0000000000010000, 0x0e8, POPA, Ot::NoOperand, Ot::NoOperand), - inst!( 0x62, 0, 0b0000000000011000, 0x0e8, BOUND, Ot::NoOperand, Ot::NoOperand), - inst!( 0x63, 0, 0b0000000000011000, 0x0e8, BOUND, Ot::NoOperand, Ot::NoOperand), + inst!( 0x62, 0, 0b0000000000011000, 0x0e8, BOUND, Ot::Register16, Ot::ModRM16), + inst!( 0x63, 0, 0b0000000000011000, 0x0e8, BOUND, Ot::Register16, Ot::ModRM16), inst!( 0x64, 0, 0b0000000000010000, 0x0e8, Prefix, Ot::NoOperand, Ot::NoOperand), inst!( 0x65, 0, 0b0000000000010000, 0x0e8, Prefix, Ot::NoOperand, Ot::NoOperand), inst!( 0x66, 0, 0b0000000000000000, 0x0e8, ESC, Ot::NoOperand, Ot::NoOperand), inst!( 0x67, 0, 0b0000000000000000, 0x0e8, ESC, Ot::NoOperand, Ot::NoOperand), - inst!( 0x68, 0, 0b0000000000000000, 0x0e8, PUSH, Ot::NoOperand, Ot::NoOperand), - inst!( 0x69, 0, 0b0000000000000000, 0x0e8, IMUL, Ot::NoOperand, Ot::NoOperand), - inst!( 0x6A, 0, 0b0000000000000000, 0x0e8, PUSH, Ot::NoOperand, Ot::NoOperand), - inst!( 0x6B, 0, 0b0000000000000000, 0x0e8, IMUL, Ot::NoOperand, Ot::NoOperand), + inst!( 0x68, 0, 0b0000000000000000, 0x0e8, PUSH, Ot::Immediate16, Ot::NoOperand), + inst!( 0x69, 0, 0b0000000000000000, 0x0e8, IMUL, Ot::Register16, Ot::ModRM16), + inst!( 0x6A, 0, 0b0000000000000000, 0x0e8, PUSH, Ot::Immediate8, Ot::NoOperand), + inst!( 0x6B, 0, 0b0000000000000000, 0x0e8, IMUL, Ot::Register16, Ot::ModRM16), inst!( 0x6C, 0, 0b0000000000000000, 0x0e8, INS, Ot::NoOperand, Ot::NoOperand), inst!( 0x6D, 0, 0b0000000000000000, 0x0e8, INS, Ot::NoOperand, Ot::NoOperand), inst!( 0x6E, 0, 0b0000000000000000, 0x0e8, OUTS, Ot::NoOperand, Ot::NoOperand), @@ -335,8 +336,8 @@ pub const DECODE: [InstTemplate; 352] = [ inst!( 0xBD, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), inst!( 0xBE, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), inst!( 0xBF, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), - inst!( 0xC0, 0, 0b0100000000110000, 0x0cc, RETN, Ot::Immediate16, Ot::NoOperand), - inst!( 0xC1, 0, 0b0100000000110000, 0x0bc, RETN, Ot::NoOperand, Ot::NoOperand), + inst!( 0xC0, 5, 0b0100000000110000, 0x0cc, Group, Ot::Immediate16, Ot::NoOperand), + inst!( 0xC1, 6, 0b0100000000110000, 0x0bc, Group, Ot::NoOperand, Ot::NoOperand), inst!( 0xC2, 0, 0b0100000000110000, 0x0cc, RETN, Ot::Immediate16, Ot::NoOperand), inst!( 0xC3, 0, 0b0100000000110000, 0x0bc, RETN, Ot::NoOperand, Ot::NoOperand), inst!( 0xC4, 0, 0b0100000000100000, 0x0f0, LES, Ot::Register16, Ot::ModRM16), @@ -351,13 +352,13 @@ pub const DECODE: [InstTemplate; 352] = [ inst!( 0xCD, 0, 0b0100000000110000, 0x1a8, INT, Ot::Immediate8, Ot::NoOperand), inst!( 0xCE, 0, 0b0100000000110000, 0x1ac, INTO, Ot::NoOperand, Ot::NoOperand), inst!( 0xCF, 0, 0b0100000000110000, 0x0c8, IRET, Ot::NoOperand, Ot::NoOperand), - inst!( 0xD0, 5, 0b0100100000000000, 0x088, ROL , Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0xD1, 6, 0b0100100000000000, 0x088, SAR , Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0xD2, 7, 0b0100100000000000, 0x08c, ROL , Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0xD3, 8, 0b0100100000000000, 0x08c, SAR , Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0xD0, 7, 0b0100100000000000, 0x088, ROL , Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0xD1, 8, 0b0100100000000000, 0x088, SAR , Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0xD2, 9, 0b0100100000000000, 0x08c, ROL , Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0xD3, 10, 0b0100100000000000, 0x08c, SAR , Group, Ot::NoOperand, Ot::NoOperand), inst!( 0xD4, 0, 0b0101000000110000, 0x174, AAM, Ot::Immediate8, Ot::NoOperand), inst!( 0xD5, 0, 0b0101000000110000, 0x170, AAD, Ot::Immediate8, Ot::NoOperand), - inst!( 0xD6, 0, 0b0101000000110000, 0x0a0, SALC, Ot::NoOperand, Ot::NoOperand), + inst!( 0xD6, 0, 0b0101000000110000, 0x0a0, XLAT, Ot::NoOperand, Ot::NoOperand), inst!( 0xD7, 0, 0b0101000000110000, 0x10c, XLAT, Ot::NoOperand, Ot::NoOperand), inst!( 0xD8, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), inst!( 0xD9, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), @@ -389,16 +390,16 @@ pub const DECODE: [InstTemplate; 352] = [ inst!( 0xF3, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand), inst!( 0xF4, 0, 0b0100010000110010, 0x1FF, HLT, Ot::NoOperand, Ot::NoOperand), inst!( 0xF5, 0, 0b0100010000110010, 0x1FF, CMC, Ot::NoOperand, Ot::NoOperand), - inst!( 0xF6, 9, 0b0100100000100100, 0x098, Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0xF7, 10, 0b0100100000100100, 0x160, Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0xF6, 11, 0b0100100000100100, 0x098, Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0xF7, 12, 0b0100100000100100, 0x160, Group, Ot::NoOperand, Ot::NoOperand), inst!( 0xF8, 0, 0b0100010001110010, 0x1FF, CLC, Ot::NoOperand, Ot::NoOperand), inst!( 0xF9, 0, 0b0100010001110010, 0x1FF, STC, Ot::NoOperand, Ot::NoOperand), inst!( 0xFA, 0, 0b0100010001110010, 0x1FF, CLI, Ot::NoOperand, Ot::NoOperand), inst!( 0xFB, 0, 0b0100010001110010, 0x1FF, STI, Ot::NoOperand, Ot::NoOperand), inst!( 0xFC, 0, 0b0100010001110010, 0x1FF, CLD, Ot::NoOperand, Ot::NoOperand), inst!( 0xFD, 0, 0b0100010001110010, 0x1FF, STD, Ot::NoOperand, Ot::NoOperand), - inst!( 0xFE, 11, 0b0000100000100100, 0x020, Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0xFF, 12, 0b0000100000100100, 0x026, Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0xFE, 13, 0b0000100000100100, 0x020, Group, Ot::NoOperand, Ot::NoOperand), + inst!( 0xFF, 14, 0b0000100000100100, 0x026, Group, Ot::NoOperand, Ot::NoOperand), // Group inst!( 0x80, 1, 0b0110100000000000, 0x00c, ADD , ADD , Ot::ModRM8, Ot::Immediate8), inst!( 0x80, 1, 0b0110100000000000, 0x00c, OR , OR , Ot::ModRM8, Ot::Immediate8), @@ -436,77 +437,95 @@ pub const DECODE: [InstTemplate; 352] = [ inst!( 0x83, 1, 0b0110100000000000, 0x00c, XOR , XOR , Ot::ModRM16, Ot::Immediate8SignExtended), inst!( 0x83, 1, 0b0110100000000000, 0x00c, CMP , CMP , Ot::ModRM16, Ot::Immediate8SignExtended), // Group - inst!( 0xD0, 2, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM8, Ot::NoOperand), - inst!( 0xD0, 2, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM8, Ot::NoOperand), - inst!( 0xD0, 2, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM8, Ot::NoOperand), - inst!( 0xD0, 2, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM8, Ot::NoOperand), - inst!( 0xD0, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM8, Ot::NoOperand), - inst!( 0xD0, 2, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM8, Ot::NoOperand), - inst!( 0xD0, 2, 0b0100100000000000, 0x088, SETMO , SETMO, Ot::ModRM8, Ot::NoOperand), - inst!( 0xD0, 2, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM8, Ot::NoOperand), + inst!( 0xC0, 2, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM8, Ot::Immediate8), + inst!( 0xC0, 2, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM8, Ot::Immediate8), + inst!( 0xC0, 2, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM8, Ot::Immediate8), + inst!( 0xC0, 2, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM8, Ot::Immediate8), + inst!( 0xC0, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM8, Ot::Immediate8), + inst!( 0xC0, 2, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM8, Ot::Immediate8), + inst!( 0xC0, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM8, Ot::Immediate8), + inst!( 0xC0, 2, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM8, Ot::Immediate8), // Group - inst!( 0xD1, 2, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM16, Ot::NoOperand), - inst!( 0xD1, 2, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM16, Ot::NoOperand), - inst!( 0xD1, 2, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM16, Ot::NoOperand), - inst!( 0xD1, 2, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM16, Ot::NoOperand), - inst!( 0xD1, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM16, Ot::NoOperand), - inst!( 0xD1, 2, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM16, Ot::NoOperand), - inst!( 0xD1, 2, 0b0100100000000000, 0x088, SETMO , SETMO, Ot::ModRM16, Ot::NoOperand), - inst!( 0xD1, 2, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM16, Ot::NoOperand), + inst!( 0xC1, 2, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM16, Ot::Immediate8), + inst!( 0xC1, 2, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM16, Ot::Immediate8), + inst!( 0xC1, 2, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM16, Ot::Immediate8), + inst!( 0xC1, 2, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM16, Ot::Immediate8), + inst!( 0xC1, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM16, Ot::Immediate8), + inst!( 0xC1, 2, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM16, Ot::Immediate8), + inst!( 0xC1, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM16, Ot::Immediate8), + inst!( 0xC1, 2, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM16, Ot::Immediate8), // Group - inst!( 0xD2, 3, 0b0100100000000000, 0x08c, ROL , ROL , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD2, 3, 0b0100100000000000, 0x08c, ROR , ROR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD2, 3, 0b0100100000000000, 0x08c, RCL , RCL , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD2, 3, 0b0100100000000000, 0x08c, RCR , RCR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD2, 3, 0b0100100000000000, 0x08c, SHL , SHL , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD2, 3, 0b0100100000000000, 0x08c, SHR , SHR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD2, 3, 0b0100100000000000, 0x08c, SETMO , SETMOC, Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD2, 3, 0b0100100000000000, 0x08c, SAR , SAR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD0, 3, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM8, Ot::NoOperand), + inst!( 0xD0, 3, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM8, Ot::NoOperand), + inst!( 0xD0, 3, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM8, Ot::NoOperand), + inst!( 0xD0, 3, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM8, Ot::NoOperand), + inst!( 0xD0, 3, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM8, Ot::NoOperand), + inst!( 0xD0, 3, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM8, Ot::NoOperand), + inst!( 0xD0, 3, 0b0100100000000000, 0x088, SETMO , SETMO, Ot::ModRM8, Ot::NoOperand), + inst!( 0xD0, 3, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM8, Ot::NoOperand), // Group - inst!( 0xD3, 3, 0b0100100000000000, 0x08c, ROL , ROL , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD3, 3, 0b0100100000000000, 0x08c, ROR , ROR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD3, 3, 0b0100100000000000, 0x08c, RCL , RCL , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD3, 3, 0b0100100000000000, 0x08c, RCR , RCR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD3, 3, 0b0100100000000000, 0x08c, SHL , SHL , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD3, 3, 0b0100100000000000, 0x08c, SHR , SHR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD3, 3, 0b0100100000000000, 0x08c, SETMO , SETMOC, Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD3, 3, 0b0100100000000000, 0x08c, SAR , SAR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD1, 3, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM16, Ot::NoOperand), + inst!( 0xD1, 3, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM16, Ot::NoOperand), + inst!( 0xD1, 3, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM16, Ot::NoOperand), + inst!( 0xD1, 3, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM16, Ot::NoOperand), + inst!( 0xD1, 3, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM16, Ot::NoOperand), + inst!( 0xD1, 3, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM16, Ot::NoOperand), + inst!( 0xD1, 3, 0b0100100000000000, 0x088, SETMO , SETMO, Ot::ModRM16, Ot::NoOperand), + inst!( 0xD1, 3, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM16, Ot::NoOperand), // Group - inst!( 0xF6, 4, 0b0100100000100100, 0x098, TEST , Ot::ModRM8, Ot::Immediate8), - inst!( 0xF6, 4, 0b0100100000100100, 0x098, TEST , Ot::ModRM8, Ot::Immediate8), - inst!( 0xF6, 4, 0b0100100000100100, 0x098, NOT , NOT , Ot::ModRM8, Ot::NoOperand), - inst!( 0xF6, 4, 0b0100100000100100, 0x098, NEG , NEG , Ot::ModRM8, Ot::NoOperand), - inst!( 0xF6, 4, 0b0100100000100100, 0x098, MUL , Ot::ModRM8, Ot::NoOperand), - inst!( 0xF6, 4, 0b0100100000100100, 0x098, IMUL , Ot::ModRM8, Ot::NoOperand), - inst!( 0xF6, 4, 0b0100100000100100, 0x098, DIV , Ot::ModRM8, Ot::NoOperand), - inst!( 0xF6, 4, 0b0100100000100100, 0x098, IDIV , Ot::ModRM8, Ot::NoOperand), + inst!( 0xD2, 4, 0b0100100000000000, 0x08c, ROL , ROL , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD2, 4, 0b0100100000000000, 0x08c, ROR , ROR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD2, 4, 0b0100100000000000, 0x08c, RCL , RCL , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD2, 4, 0b0100100000000000, 0x08c, RCR , RCR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD2, 4, 0b0100100000000000, 0x08c, SHL , SHL , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD2, 4, 0b0100100000000000, 0x08c, SHR , SHR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD2, 4, 0b0100100000000000, 0x08c, SETMO , SETMOC, Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD2, 4, 0b0100100000000000, 0x08c, SAR , SAR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), // Group - inst!( 0xF7, 4, 0b0100100000100100, 0x160, TEST , Ot::ModRM16, Ot::Immediate16), - inst!( 0xF7, 4, 0b0100100000100100, 0x160, TEST , Ot::ModRM16, Ot::Immediate16), - inst!( 0xF7, 4, 0b0100100000100100, 0x160, NOT , NOT , Ot::ModRM16, Ot::NoOperand), - inst!( 0xF7, 4, 0b0100100000100100, 0x160, NEG , NEG , Ot::ModRM16, Ot::NoOperand), - inst!( 0xF7, 4, 0b0100100000100100, 0x160, MUL , Ot::ModRM16, Ot::NoOperand), - inst!( 0xF7, 4, 0b0100100000100100, 0x160, IMUL , Ot::ModRM16, Ot::NoOperand), - inst!( 0xF7, 4, 0b0100100000100100, 0x160, DIV , Ot::ModRM16, Ot::NoOperand), - inst!( 0xF7, 4, 0b0100100000100100, 0x160, IDIV , Ot::ModRM16, Ot::NoOperand), + inst!( 0xD3, 4, 0b0100100000000000, 0x08c, ROL , ROL , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD3, 4, 0b0100100000000000, 0x08c, ROR , ROR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD3, 4, 0b0100100000000000, 0x08c, RCL , RCL , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD3, 4, 0b0100100000000000, 0x08c, RCR , RCR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD3, 4, 0b0100100000000000, 0x08c, SHL , SHL , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD3, 4, 0b0100100000000000, 0x08c, SHR , SHR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD3, 4, 0b0100100000000000, 0x08c, SETMO , SETMOC, Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD3, 4, 0b0100100000000000, 0x08c, SAR , SAR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), // Group - inst!( 0xFE, 5, 0b0000100000100100, 0x020, INC , INC , Ot::ModRM8, Ot::NoOperand), - inst!( 0xFE, 5, 0b0000100000100100, 0x020, DEC , DEC , Ot::ModRM8, Ot::NoOperand), - inst!( 0xFE, 5, 0b0000100000100100, 0x020, CALL , Ot::ModRM8, Ot::NoOperand), - inst!( 0xFE, 5, 0b0000100000100100, 0x020, CALLF , Ot::ModRM8, Ot::NoOperand), - inst!( 0xFE, 5, 0b0000100000100100, 0x020, JMP , Ot::ModRM8, Ot::NoOperand), - inst!( 0xFE, 5, 0b0000100000100100, 0x020, JMPF , Ot::ModRM8, Ot::NoOperand), - inst!( 0xFE, 5, 0b0000100000100100, 0x020, PUSH , Ot::ModRM8, Ot::NoOperand), - inst!( 0xFE, 5, 0b0000100000100100, 0x020, PUSH , Ot::ModRM8, Ot::NoOperand), + inst!( 0xF6, 5, 0b0100100000100100, 0x098, TEST , Ot::ModRM8, Ot::Immediate8), + inst!( 0xF6, 5, 0b0100100000100100, 0x098, TEST , Ot::ModRM8, Ot::Immediate8), + inst!( 0xF6, 5, 0b0100100000100100, 0x098, NOT , NOT , Ot::ModRM8, Ot::NoOperand), + inst!( 0xF6, 5, 0b0100100000100100, 0x098, NEG , NEG , Ot::ModRM8, Ot::NoOperand), + inst!( 0xF6, 5, 0b0100100000100100, 0x098, MUL , Ot::ModRM8, Ot::NoOperand), + inst!( 0xF6, 5, 0b0100100000100100, 0x098, IMUL , Ot::ModRM8, Ot::NoOperand), + inst!( 0xF6, 5, 0b0100100000100100, 0x098, DIV , Ot::ModRM8, Ot::NoOperand), + inst!( 0xF6, 5, 0b0100100000100100, 0x098, IDIV , Ot::ModRM8, Ot::NoOperand), // Group - inst!( 0xFF, 5, 0b0000100000100100, 0x026, INC , INC , Ot::ModRM16, Ot::NoOperand), - inst!( 0xFF, 5, 0b0000100000100100, 0x026, DEC , DEC , Ot::ModRM16, Ot::NoOperand), - inst!( 0xFF, 5, 0b0000100000100100, 0x026, CALL , Ot::ModRM16, Ot::NoOperand), - inst!( 0xFF, 5, 0b0000100000100100, 0x026, CALLF , Ot::ModRM16, Ot::NoOperand), - inst!( 0xFF, 5, 0b0000100000100100, 0x026, JMP , Ot::ModRM16, Ot::NoOperand), - inst!( 0xFF, 5, 0b0000100000100100, 0x026, JMPF , Ot::ModRM16, Ot::NoOperand), - inst!( 0xFF, 5, 0b0000100000100100, 0x026, PUSH , Ot::ModRM16, Ot::NoOperand), - inst!( 0xFF, 5, 0b0000100000100100, 0x026, PUSH , Ot::ModRM16, Ot::NoOperand), + inst!( 0xF7, 5, 0b0100100000100100, 0x160, TEST , Ot::ModRM16, Ot::Immediate16), + inst!( 0xF7, 5, 0b0100100000100100, 0x160, TEST , Ot::ModRM16, Ot::Immediate16), + inst!( 0xF7, 5, 0b0100100000100100, 0x160, NOT , NOT , Ot::ModRM16, Ot::NoOperand), + inst!( 0xF7, 5, 0b0100100000100100, 0x160, NEG , NEG , Ot::ModRM16, Ot::NoOperand), + inst!( 0xF7, 5, 0b0100100000100100, 0x160, MUL , Ot::ModRM16, Ot::NoOperand), + inst!( 0xF7, 5, 0b0100100000100100, 0x160, IMUL , Ot::ModRM16, Ot::NoOperand), + inst!( 0xF7, 5, 0b0100100000100100, 0x160, DIV , Ot::ModRM16, Ot::NoOperand), + inst!( 0xF7, 5, 0b0100100000100100, 0x160, IDIV , Ot::ModRM16, Ot::NoOperand), + // Group + inst!( 0xFE, 6, 0b0000100000100100, 0x020, INC , INC , Ot::ModRM8, Ot::NoOperand), + inst!( 0xFE, 6, 0b0000100000100100, 0x020, DEC , DEC , Ot::ModRM8, Ot::NoOperand), + inst!( 0xFE, 6, 0b0000100000100100, 0x020, CALL , Ot::ModRM8, Ot::NoOperand), + inst!( 0xFE, 6, 0b0000100000100100, 0x020, CALLF , Ot::ModRM8, Ot::NoOperand), + inst!( 0xFE, 6, 0b0000100000100100, 0x020, JMP , Ot::ModRM8, Ot::NoOperand), + inst!( 0xFE, 6, 0b0000100000100100, 0x020, JMPF , Ot::ModRM8, Ot::NoOperand), + inst!( 0xFE, 6, 0b0000100000100100, 0x020, PUSH , Ot::ModRM8, Ot::NoOperand), + inst!( 0xFE, 6, 0b0000100000100100, 0x020, PUSH , Ot::ModRM8, Ot::NoOperand), + // Group + inst!( 0xFF, 6, 0b0000100000100100, 0x026, INC , INC , Ot::ModRM16, Ot::NoOperand), + inst!( 0xFF, 6, 0b0000100000100100, 0x026, DEC , DEC , Ot::ModRM16, Ot::NoOperand), + inst!( 0xFF, 6, 0b0000100000100100, 0x026, CALL , Ot::ModRM16, Ot::NoOperand), + inst!( 0xFF, 6, 0b0000100000100100, 0x026, CALLF , Ot::ModRM16, Ot::NoOperand), + inst!( 0xFF, 6, 0b0000100000100100, 0x026, JMP , Ot::ModRM16, Ot::NoOperand), + inst!( 0xFF, 6, 0b0000100000100100, 0x026, JMPF , Ot::ModRM16, Ot::NoOperand), + inst!( 0xFF, 6, 0b0000100000100100, 0x026, PUSH , Ot::ModRM16, Ot::NoOperand), + inst!( 0xFF, 6, 0b0000100000100100, 0x026, PUSH , Ot::ModRM16, Ot::NoOperand), ]; impl NecVx0 { @@ -751,6 +770,15 @@ impl NecVx0 { size += 4; (OperandType::FarAddress(0,0), OperandSize::NoSize) } + (OperandTemplate::M16Pair, true) => { + let (int0, int1) = bytes.q_peek_farptr16(); + size += 4; + (OperandType::M16Pair(int0,int1), OperandSize::NoSize) + } + (OperandTemplate::M16Pair, false) => { + size += 4; + (OperandType::M16Pair(0,0), OperandSize::NoSize) + } _ => (OperandType::NoOperand,OperandSize::NoOperand) } }; @@ -761,6 +789,19 @@ impl NecVx0 { if !matches!(op_lu.operand2, OperandTemplate::NoTemplate) { (operand2_type, operand2_size) = match_op(op_lu.operand2); } + + // Hack for 3-operand instructions + match opcode { + 0x69 => { + // imm16 + size += 2; + } + 0x6B => { + // imm8 + size += 1; + } + _ => {} + } // Disabled: Decode cannot fail, but this is a placeholder for future error handling in other CPUs /* diff --git a/core/src/cpu_vx0/execute.rs b/core/src/cpu_vx0/execute.rs index 28905a7b..d8f6709c 100644 --- a/core/src/cpu_vx0/execute.rs +++ b/core/src/cpu_vx0/execute.rs @@ -400,8 +400,89 @@ impl NecVx0 { self.push_register16(Register16::SI, ReadWriteFlag::Normal); self.push_register16(Register16::DI, ReadWriteFlag::RNI); } - 0x61..=0x6F => { + 0x61 => { + // POPA + self.pop_register16(Register16::DI, ReadWriteFlag::Normal); + self.pop_register16(Register16::SI, ReadWriteFlag::Normal); + self.pop_register16(Register16::BP, ReadWriteFlag::Normal); + self.set_register16(Register16::SP, self.get_register16(Register16::SP).wrapping_add(2)); + self.pop_register16(Register16::BX, ReadWriteFlag::Normal); + self.pop_register16(Register16::DX, ReadWriteFlag::Normal); + self.pop_register16(Register16::CX, ReadWriteFlag::Normal); + self.pop_register16(Register16::AX, ReadWriteFlag::RNI); + } + 0x62 => { + // BOUND + let idx = self.read_operand16(self.i.operand1_type, None).unwrap() as i16; + let bounds_opt = self.read_operand_m16m16(self.i.operand2_type, self.i.segment_override, ReadWriteFlag::Normal); + + if let Some((start_u, end_u)) = bounds_opt { + + let start_i = start_u as i16; + let end_i = end_u as i16; + log::warn!("BOUND: Bounds: {} <-> {}", start_u, end_u); + + if (idx >= start_i) && (idx <= end_i) { + log::warn!("BOUND: In bounds: {} <= {} <= {}", start_i, idx, end_i); + // In bounds! + } + else { + log::warn!("BOUND: Out of bounds: {} <= {} <= {}", start_i, idx, end_i); + // Bounds range exception + self.sw_interrupt(5); + exception = CpuException::BoundsException; + jump = true; + } + } + else { + // Illegal form of BOUND. A real V20 will halt now. + log::warn!("BOUND: Illegal form of BOUND!"); + self.halted = true; + } + } + 0x68 => { + // PUSH imm16 + let imm16 = self.read_operand16(self.i.operand1_type, None).unwrap(); + self.push_u16(imm16, ReadWriteFlag::RNI); + } + 0x69 => { + // IMUL r16, r/m16, imm16 + let op3_value = self.q_read_u16(QueueType::Subsequent, QueueReader::Eu); + let mul_op = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + // Truncate product. + let (_, product) = self.mul16(mul_op, op3_value, true, false); + + self.write_operand16(self.i.operand1_type, None, product, ReadWriteFlag::RNI); + + if let OperandType::Register8(_) = self.i.operand1_type { + self.cycle(); + } + + self.set_szp_flags_from_result_u8(product as u8); + } + 0x6A => { + // PUSH imm8 + let imm8 = self.read_operand8(self.i.operand1_type, None).unwrap(); + self.push_u16(imm8 as u16, ReadWriteFlag::RNI); + } + 0x6B => { + // IMUL r16, r/m16, imm8 + // Sign extend immediate byte operand. + let op3_value = self.q_read_u8(QueueType::Subsequent, QueueReader::Eu) as i8 as i16; + let mul_op = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); + // Truncate product. + let (_, product) = self.mul16(mul_op, op3_value as u16, true, false); + + self.write_operand16(self.i.operand1_type, None, product, ReadWriteFlag::RNI); + + if let OperandType::Register8(_) = self.i.operand1_type { + self.cycle(); + } + + self.set_szp_flags_from_result_u8(product as u8); + } + 0x63..=0x6F => { unhandled = true; } 0x70..=0x7F => { @@ -924,7 +1005,53 @@ impl NecVx0 { } //self.cycle_i(0x01e); } - 0xC0 | 0xC2 => { + 0xC0 => { + // ROL, ROR, RCL, RCR, SHL, SHR, SAR: r/m8, imm8 + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + + self.cycles_i(6, &[0x08c, 0x08d, 0x08e, MC_JUMP, 0x090, 0x091]); + + if op2_value > 0 { + for _ in 0..op2_value { + self.cycles_i(4, &[MC_JUMP, 0x08f, 0x090, 0x091]); + } + } + + // If there is a terminal write to M, don't process RNI on line 0x92 + if let OperandType::AddressingMode(_) = self.i.operand1_type { + //if self.c.l() != 0 { + self.cycle_i(0x092); + //} + } + + let result = self.bitshift_op8(self.i.mnemonic, op1_value, op2_value); + + self.write_operand8(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + } + 0xC1 => { + // ROL, ROR, RCL, RCR, SHL, SHR, SAR: r/m16, imm8 + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); + + self.cycles_i(6, &[0x08c, 0x08d, 0x08e, MC_JUMP, 0x090, 0x091]); + + if op2_value > 0 { + for _ in 0..op2_value { + self.cycles_i(4, &[MC_JUMP, 0x08f, 0x090, 0x091]); + } + } + + // If there is a terminal write to M, don't process RNI on line 0x92 + if let OperandType::AddressingMode(_) = self.i.operand1_type { + self.cycle_i(0x092); + } + + let result = self.bitshift_op16(self.i.mnemonic, op1_value, op2_value); + + self.write_operand16(self.i.operand1_type, self.i.segment_override, result, ReadWriteFlag::RNI); + } + 0xC2 => { // RETN imm16 - Return from call w/ release // 0xC0 undocumented alias for 0xC2 // Flags: None @@ -946,7 +1073,7 @@ impl NecVx0 { jump = true } - 0xC1 | 0xC3 => { + 0xC3 => { // RETN - Return from call // 0xC1 undocumented alias for 0xC3 // Flags: None @@ -1164,18 +1291,7 @@ impl NecVx0 { let op1_value = self.read_operand8(self.i.operand1_type, None).unwrap(); self.aad(op1_value); } - 0xD6 => { - // SALC - Undocumented Opcode - Set Carry flag in AL - // http://www.rcollins.org/secrets/opcodes/SALC.html - - self.set_register8(Register8::AL, - match self.get_flag(Flag::Carry) { - true => 0xFF, - false => 0 - } - ); - } - 0xD7 => { + 0xD6 | 0xD7 => { // XLAT // Handle segment override, default DS @@ -2093,6 +2209,7 @@ impl NecVx0 { else { match exception { CpuException::DivideError => ExecutionResult::ExceptionError(exception), + CpuException::BoundsException => ExecutionResult::ExceptionError(exception), CpuException::NoException => Okay, } } diff --git a/core/src/cpu_vx0/mod.rs b/core/src/cpu_vx0/mod.rs index bf443e0c..5927d451 100644 --- a/core/src/cpu_vx0/mod.rs +++ b/core/src/cpu_vx0/mod.rs @@ -834,7 +834,11 @@ impl NecVx0 { { cpu.validator = match validator_type { #[cfg(feature = "arduino_validator")] - ValidatorType::Arduino8088 => Some(Box::new(ArduinoValidator::new(validator_trace, validator_baud))), + ValidatorType::Arduino8088 => Some(Box::new(ArduinoValidator::new( + cpu_type, + validator_trace, + validator_baud, + ))), _ => None, }; diff --git a/frontends/martypc_desktop_wgpu/src/cpu_test/gen_tests.rs b/frontends/martypc_desktop_wgpu/src/cpu_test/gen_tests.rs index eb231f38..afde0303 100644 --- a/frontends/martypc_desktop_wgpu/src/cpu_test/gen_tests.rs +++ b/frontends/martypc_desktop_wgpu/src/cpu_test/gen_tests.rs @@ -80,12 +80,12 @@ pub fn run_gentests(config: &ConfigFileParams) { } let trace_mode = config.machine.cpu.trace_mode.unwrap_or_default(); - + let cpu_type = config.tests.test_cpu_type.unwrap_or(CpuType::Intel8088); let mut cpu; #[cfg(feature = "cpu_validator")] { cpu = match CpuBuilder::new() - .with_cpu_type(config.tests.test_cpu_type.unwrap_or(CpuType::Intel8088)) + .with_cpu_type(cpu_type) //.with_cpu_subtype(CpuSubType::Intel8088) .with_trace_mode(trace_mode) .with_trace_logger(trace_logger) @@ -173,7 +173,7 @@ pub fn run_gentests(config: &ConfigFileParams) { test_base_path.push(test_path_postfix); for test_opcode in opcode_list { - let is_grp = ArduinoValidator::is_group_opcode(test_opcode); + let is_grp = ArduinoValidator::is_group_opcode(cpu_type, test_opcode); let mut start_ext = 0; let mut end_ext = if is_grp { 7 } else { 0 }; @@ -358,9 +358,14 @@ pub fn run_gentests(config: &ConfigFileParams) { i.address = instruction_address; + let bytes = cpu + .bus() + .peek_range(instruction_address as usize, i.size as usize) + .unwrap(); + println!( - "Test {}: Creating test for instruction: {} opcode:{:02X} addr:{:05X}", - test_num, i, opcode, i.address + "Test {}: Creating test for instruction: {} opcode:{:02X} addr:{:05X} bytes: {:X?}", + test_num, i, opcode, i.address, bytes ); // Set terminating address for CPU validator. @@ -442,7 +447,7 @@ pub fn run_gentests(config: &ConfigFileParams) { let test_elapsed = test_start_instant.elapsed().as_secs_f32(); println!( - "Test generation complete for opcode: {:02}. Generated {} tests in {:.2} seconds", + "Test generation complete for opcode: {:02X}. Generated {} tests in {:.2} seconds", test_opcode, test_num, test_elapsed ); let avg_test_elapsed = test_elapsed / test_num as f32; From 82747933decbae118b274241ec40def6e39b8856 Mon Sep 17 00:00:00 2001 From: dbalsom Date: Thu, 9 May 2024 09:01:18 -0400 Subject: [PATCH 06/11] more v20 test work (cont...) --- core/src/cpu_808x/cpu.rs | 2 +- core/src/cpu_808x/decode.rs | 16 +- core/src/cpu_808x/execute.rs | 12 +- core/src/cpu_808x/mod.rs | 19 - core/src/cpu_common/addressing.rs | 8 +- core/src/cpu_common/instruction.rs | 75 +- core/src/cpu_common/mnemonic.rs | 49 +- core/src/cpu_common/mod.rs | 21 +- core/src/cpu_common/operands.rs | 2 +- core/src/cpu_vx0/addressing.rs | 6 +- core/src/cpu_vx0/alu.rs | 1 + core/src/cpu_vx0/biu.rs | 7 +- core/src/cpu_vx0/cpu.rs | 4 +- core/src/cpu_vx0/decode.rs | 842 ++++++++++-------- core/src/cpu_vx0/execute.rs | 209 ++++- core/src/cpu_vx0/fuzzer.rs | 73 +- core/src/cpu_vx0/gdr.rs | 1 + core/src/cpu_vx0/mod.rs | 21 +- core/src/cpu_vx0/step.rs | 9 +- core/src/cpu_vx0/string.rs | 60 ++ .../src/cpu_test/common.rs | 2 +- .../src/cpu_test/gen_tests.rs | 11 +- .../src/cpu_test/run_tests.rs | 197 ++-- lib/frontend/config_toml_bpaf/Cargo.toml | 4 +- .../config_toml_bpaf/src/coreconfig.rs | 2 +- lib/frontend/config_toml_bpaf/src/lib.rs | 8 +- 26 files changed, 1046 insertions(+), 615 deletions(-) diff --git a/core/src/cpu_808x/cpu.rs b/core/src/cpu_808x/cpu.rs index c04dd34e..40362c5b 100644 --- a/core/src/cpu_808x/cpu.rs +++ b/core/src/cpu_808x/cpu.rs @@ -521,7 +521,7 @@ impl Cpu for Intel808x { self.random_grp_instruction(opcode, extension_list) } - fn random_inst_from_opcodes(&mut self, opcode_list: &[u8]) { + fn random_inst_from_opcodes(&mut self, opcode_list: &[u8], _prefix: Option) { self.random_inst_from_opcodes(opcode_list); } } diff --git a/core/src/cpu_808x/decode.rs b/core/src/cpu_808x/decode.rs index 262aa3f6..f26bc3d0 100644 --- a/core/src/cpu_808x/decode.rs +++ b/core/src/cpu_808x/decode.rs @@ -40,7 +40,21 @@ use std::{error::Error, fmt::Display}; use crate::{ bytequeue::*, cpu_808x::{alu::Xi, gdr::GdrEntry, modrm::ModRmByte, *}, - cpu_common::{operands::OperandSize, AddressingMode, Instruction, Mnemonic, OperandType, Segment}, + cpu_common::{ + operands::OperandSize, + AddressingMode, + Instruction, + Mnemonic, + OperandType, + Segment, + OPCODE_PREFIX_CS_OVERRIDE, + OPCODE_PREFIX_DS_OVERRIDE, + OPCODE_PREFIX_ES_OVERRIDE, + OPCODE_PREFIX_LOCK, + OPCODE_PREFIX_REP1, + OPCODE_PREFIX_REP2, + OPCODE_PREFIX_SS_OVERRIDE, + }, }; #[derive(Copy, Clone, PartialEq)] diff --git a/core/src/cpu_808x/execute.rs b/core/src/cpu_808x/execute.rs index 2cc2e157..545a469a 100644 --- a/core/src/cpu_808x/execute.rs +++ b/core/src/cpu_808x/execute.rs @@ -32,7 +32,17 @@ use crate::{ cpu_808x::{biu::*, *}, - cpu_common::{CpuAddress, CpuException, ExecutionResult, Mnemonic, OperandType, QueueOp, Segment}, + cpu_common::{ + CpuAddress, + CpuException, + ExecutionResult, + Mnemonic, + OperandType, + QueueOp, + Segment, + OPCODE_PREFIX_REP1, + OPCODE_PREFIX_REP2, + }, util, }; diff --git a/core/src/cpu_808x/mod.rs b/core/src/cpu_808x/mod.rs index 68ac1590..0f917b66 100644 --- a/core/src/cpu_808x/mod.rs +++ b/core/src/cpu_808x/mod.rs @@ -172,25 +172,6 @@ pub const MAX_INSTRUCTION_SIZE: usize = 15; const OPCODE_REGISTER_SELECT_MASK: u8 = 0b0000_0111; -// Instruction flags -const I_USES_MEM: u32 = 0b0000_0001; // Instruction has a memory operand -const I_HAS_MODRM: u32 = 0b0000_0010; // Instruction has a modrm byte -const I_LOCKABLE: u32 = 0b0000_0100; // Instruction compatible with LOCK prefix -const I_REL_JUMP: u32 = 0b0000_1000; -const I_LOAD_EA: u32 = 0b0001_0000; // Instruction loads from its effective address -const I_GROUP_DELAY: u32 = 0b0010_0000; // Instruction has cycle delay for being a specific group instruction - -// Instruction prefixes -pub const OPCODE_PREFIX_ES_OVERRIDE: u32 = 0b_0000_0000_0001; -pub const OPCODE_PREFIX_CS_OVERRIDE: u32 = 0b_0000_0000_0010; -pub const OPCODE_PREFIX_SS_OVERRIDE: u32 = 0b_0000_0000_0100; -pub const OPCODE_PREFIX_DS_OVERRIDE: u32 = 0b_0000_0000_1000; -pub const OPCODE_SEG_OVERRIDE_MASK: u32 = 0b_0000_0000_1111; -pub const OPCODE_PREFIX_WAIT: u32 = 0b_0000_0100_0000; -pub const OPCODE_PREFIX_LOCK: u32 = 0b_0000_1000_0000; -pub const OPCODE_PREFIX_REP1: u32 = 0b_0001_0000_0000; -pub const OPCODE_PREFIX_REP2: u32 = 0b_0010_0000_0000; - // The parity flag is calculated from the lower 8 bits of an alu operation regardless // of the operand width. It is trivial to precalculate an 8-bit parity table. pub const PARITY_TABLE: [bool; 256] = { diff --git a/core/src/cpu_common/addressing.rs b/core/src/cpu_common/addressing.rs index d4dd9403..175ead01 100644 --- a/core/src/cpu_common/addressing.rs +++ b/core/src/cpu_common/addressing.rs @@ -30,7 +30,7 @@ */ -use crate::cpu_vx0::NecVx0; +use crate::cpu_common::calc_linear_address; use std::{fmt, fmt::Display}; #[derive(Copy, Clone, Debug)] @@ -203,7 +203,7 @@ impl From for u32 { fn from(cpu_address: CpuAddress) -> Self { match cpu_address { CpuAddress::Flat(a) => a, - CpuAddress::Segmented(s, o) => NecVx0::calc_linear_address(s, o), + CpuAddress::Segmented(s, o) => calc_linear_address(s, o), CpuAddress::Offset(a) => a as Self, } } @@ -224,12 +224,12 @@ impl PartialEq for CpuAddress { match (self, other) { (CpuAddress::Flat(a), CpuAddress::Flat(b)) => a == b, (CpuAddress::Flat(a), CpuAddress::Segmented(s, o)) => { - let b = NecVx0::calc_linear_address(*s, *o); + let b = calc_linear_address(*s, *o); *a == b } (CpuAddress::Flat(_a), CpuAddress::Offset(_b)) => false, (CpuAddress::Segmented(s, o), CpuAddress::Flat(b)) => { - let a = NecVx0::calc_linear_address(*s, *o); + let a = calc_linear_address(*s, *o); a == *b } (CpuAddress::Segmented(s1, o1), CpuAddress::Segmented(s2, o2)) => *s1 == *s2 && *o1 == *o2, diff --git a/core/src/cpu_common/instruction.rs b/core/src/cpu_common/instruction.rs index 69bd42cc..a923b3d6 100644 --- a/core/src/cpu_common/instruction.rs +++ b/core/src/cpu_common/instruction.rs @@ -35,10 +35,20 @@ use std::{ fmt::{Display, Formatter, Result as fmtResult}, }; -use super::{addressing::WithPlusSign, mnemonic::mnemonic_to_str}; +use super::{addressing::WithPlusSign, mnemonic::mnemonic_to_str, OPCODE_PREFIX_REP3, OPCODE_PREFIX_REP4}; use crate::{ - cpu_808x::{OPCODE_PREFIX_LOCK, OPCODE_PREFIX_REP1, OPCODE_PREFIX_REP2}, - cpu_common::{operands::OperandSize, AddressingMode, Mnemonic, OperandType, Register16, Register8, Segment}, + cpu_common::{ + operands::OperandSize, + AddressingMode, + Mnemonic, + OperandType, + Register16, + Register8, + Segment, + OPCODE_PREFIX_LOCK, + OPCODE_PREFIX_REP1, + OPCODE_PREFIX_REP2, + }, syntax_token::{SyntaxFormatType, SyntaxToken, SyntaxTokenVec, SyntaxTokenize}, }; @@ -100,12 +110,12 @@ impl Display for Instruction { instruction_string.push_str(&p); instruction_string.push_str(" "); } - instruction_string.push_str(&mnemonic); // Dont sign-extend 8-bit port addresses. let op_size = match self.mnemonic { Mnemonic::IN | Mnemonic::OUT => OperandSize::Operand8, + Mnemonic::ENTER => OperandSize::Operand8, _ => self.operand1_size, }; @@ -635,16 +645,17 @@ fn tokenize_operand(i: &Instruction, op: OperandSelect, lvalue: OperandSize) -> } fn override_prefix_to_string(i: &Instruction) -> Option { - if i.segment_override.is_some() { + if let Some(seg_override) = i.segment_override { match i.opcode { 0xA4 | 0xA5 | 0xAA | 0xAB | 0xAC | 0xAD | 0xA6 | 0xA7 | 0xAE | 0xAF => { - let segment: String = match i.segment_override { - Some(Segment::ES) => "es".to_string(), - Some(Segment::CS) => "cs".to_string(), - Some(Segment::SS) => "ss".to_string(), - _ => "ds".to_string(), + let segment = match seg_override { + Segment::ES => "es", + Segment::CS => "cs", + Segment::SS => "ss", + Segment::DS => "ds", + Segment::None => return None, }; - Some(segment) + Some(segment.to_string()) } _ => None, } @@ -657,28 +668,46 @@ fn override_prefix_to_string(i: &Instruction) -> Option { fn prefix_to_string(i: &Instruction) -> Option { // Handle REPx prefixes - // TODO: IS F2 valid on 6C, 6D, etc? + + let mut prefix_str = String::new(); if i.prefixes & OPCODE_PREFIX_LOCK != 0 { - Some("lock".to_string()) + prefix_str.push_str("lock "); } - else if i.prefixes & OPCODE_PREFIX_REP1 != 0 { + + if i.prefixes & OPCODE_PREFIX_REP1 != 0 { match i.opcode { - 0xF6 | 0xF7 => None, // Don't show REP prefix on div. - 0xA4 | 0xA5 | 0xAA | 0xAB | 0xAC | 0xAD => Some("rep".to_string()), - 0xA6 | 0xA7 | 0xAE | 0xAF => Some("repne".to_string()), - _ => None, + 0xA4 | 0xA5 | 0xAA | 0xAB | 0xAC | 0xAD => prefix_str.push_str("rep"), + 0xA6 | 0xA7 | 0xAE | 0xAF => prefix_str.push_str("repne"), + _ => {} } } else if i.prefixes & OPCODE_PREFIX_REP2 != 0 { match i.opcode { - 0xF6 | 0xF7 => None, // Don't show REP prefix on div. - 0xA4 | 0xA5 | 0xAA | 0xAB | 0xAC | 0xAD => Some("rep".to_string()), - 0xA6 | 0xA7 | 0xAE | 0xAF => Some("repe".to_string()), - _ => None, + 0xA4 | 0xA5 | 0xAA | 0xAB | 0xAC | 0xAD => prefix_str.push_str("rep"), + 0xA6 | 0xA7 | 0xAE | 0xAF => prefix_str.push_str("repe"), + _ => {} } } - else { + else if i.prefixes & OPCODE_PREFIX_REP3 != 0 { + match i.opcode { + 0xA4 | 0xA5 | 0xAA | 0xAB | 0xAC | 0xAD => prefix_str.push_str("rep"), + 0xA6 | 0xA7 | 0xAE | 0xAF => prefix_str.push_str("repnc"), + _ => {} + } + } + else if i.prefixes & OPCODE_PREFIX_REP4 != 0 { + match i.opcode { + 0xA4 | 0xA5 | 0xAA | 0xAB | 0xAC | 0xAD => prefix_str.push_str("rep"), + 0xA6 | 0xA7 | 0xAE | 0xAF => prefix_str.push_str("repc"), + _ => {} + } + } + + if prefix_str.is_empty() { None } + else { + Some(prefix_str) + } } diff --git a/core/src/cpu_common/mnemonic.rs b/core/src/cpu_common/mnemonic.rs index a4432622..1d2dc6ed 100644 --- a/core/src/cpu_common/mnemonic.rs +++ b/core/src/cpu_common/mnemonic.rs @@ -35,7 +35,7 @@ use std::fmt; #[allow(dead_code)] #[derive(PartialEq, Copy, Clone, Debug)] pub enum Mnemonic { - InvalidOpcode, + Invalid, NoOpcode, Group, Prefix, @@ -147,13 +147,31 @@ pub enum Mnemonic { PUSHA, POPA, BOUND, - INS, - OUTS, + INSB, + INSW, + OUTSB, + OUTSW, + ENTER, + LEAVE, + // V20 Instructions + FPO2, + TEST1, + CLR1, + SET1, + NOT1, + ADD4S, + SUB4S, + CMP4S, + ROL4, + ROR4, + BINS, + BEXT, + BRKEM, } impl Default for Mnemonic { fn default() -> Self { - Mnemonic::InvalidOpcode + Mnemonic::Invalid } } @@ -268,9 +286,26 @@ pub(crate) fn mnemonic_to_str(op: Mnemonic) -> &'static str { Mnemonic::PUSHA => "PUSHA", Mnemonic::POPA => "POPA", Mnemonic::BOUND => "BOUND", - Mnemonic::INS => "INS", - Mnemonic::OUTS => "OUTS", - + Mnemonic::INSB => "INSB", + Mnemonic::INSW => "INSW", + Mnemonic::OUTSB => "OUTSB", + Mnemonic::OUTSW => "OUTSW", + Mnemonic::ENTER => "ENTER", + Mnemonic::LEAVE => "LEAVE", + // V20 Instructions + Mnemonic::FPO2 => "FPO2", + Mnemonic::TEST1 => "TEST1", + Mnemonic::CLR1 => "CLR1", + Mnemonic::SET1 => "SET1", + Mnemonic::NOT1 => "NOT1", + Mnemonic::ADD4S => "ADD4S", + Mnemonic::SUB4S => "SUB4S", + Mnemonic::CMP4S => "CMP4S", + Mnemonic::ROL4 => "ROL4", + Mnemonic::ROR4 => "ROR4", + Mnemonic::BINS => "BINS", + Mnemonic::BEXT => "BEXT", + Mnemonic::BRKEM => "BRKEM", _ => "INVALID", } } diff --git a/core/src/cpu_common/mod.rs b/core/src/cpu_common/mod.rs index 6a6c3a38..29421943 100644 --- a/core/src/cpu_common/mod.rs +++ b/core/src/cpu_common/mod.rs @@ -63,6 +63,23 @@ use crate::{ #[cfg(feature = "cpu_validator")] use crate::cpu_validator::CpuValidator; +// Instruction prefixes +pub const OPCODE_PREFIX_0F: u32 = 0b_1000_0000_0000_0000; +pub const OPCODE_PREFIX_ES_OVERRIDE: u32 = 0b_0000_0000_0100; +pub const OPCODE_PREFIX_CS_OVERRIDE: u32 = 0b_0000_0000_1000; +pub const OPCODE_PREFIX_SS_OVERRIDE: u32 = 0b_0000_0001_0000; +pub const OPCODE_PREFIX_DS_OVERRIDE: u32 = 0b_0000_0010_0000; +pub const OPCODE_SEG_OVERRIDE_MASK: u32 = 0b_0000_0011_1100; +pub const OPCODE_PREFIX_LOCK: u32 = 0b_0000_1000_0000; +pub const OPCODE_PREFIX_REP1: u32 = 0b_0001_0000_0000; +pub const OPCODE_PREFIX_REP2: u32 = 0b_0010_0000_0000; +pub const OPCODE_PREFIX_REP3: u32 = 0b_0100_0000_0000; +pub const OPCODE_PREFIX_REP4: u32 = 0b_1000_0000_0000; +pub const OPCODE_PREFIX_REPMASK: u32 = 0b1111_0000_0000; +// Some CPUs can restore up to 3 prefixes when returning to an interrupted string operation. +// The first two bits of the prefixes field stores the number of prefixes to restore from 0-3. +pub const OPCODE_PREFIX_CT_MASK: u32 = 0b0000_0000_0011; + #[derive(Debug, Default, PartialEq)] pub enum ExecutionResult { #[default] @@ -82,7 +99,7 @@ pub enum CpuException { BoundsException, } -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum Register8 { AL, CL, @@ -362,5 +379,5 @@ pub trait Cpu { fn randomize_mem(&mut self); fn randomize_regs(&mut self); fn random_grp_instruction(&mut self, opcode: u8, extension_list: &[u8]); - fn random_inst_from_opcodes(&mut self, opcode_list: &[u8]); + fn random_inst_from_opcodes(&mut self, opcode_list: &[u8], prefix: Option); } diff --git a/core/src/cpu_common/operands.rs b/core/src/cpu_common/operands.rs index ec002cc9..4013ffb1 100644 --- a/core/src/cpu_common/operands.rs +++ b/core/src/cpu_common/operands.rs @@ -32,7 +32,7 @@ use crate::cpu_common::{AddressingMode, Register16, Register8}; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum OperandType { Immediate8(u8), Immediate16(u16), diff --git a/core/src/cpu_vx0/addressing.rs b/core/src/cpu_vx0/addressing.rs index bfe6f6d6..2948e0e1 100644 --- a/core/src/cpu_vx0/addressing.rs +++ b/core/src/cpu_vx0/addressing.rs @@ -223,7 +223,11 @@ impl NecVx0 { } Some((self.ea_opr & 0xFF) as u8) } - _ => None, + _ => { + log::error!("Bad operand type: {:?}", operand); + None + } + } } diff --git a/core/src/cpu_vx0/alu.rs b/core/src/cpu_vx0/alu.rs index 817b07d9..5f88f2fe 100644 --- a/core/src/cpu_vx0/alu.rs +++ b/core/src/cpu_vx0/alu.rs @@ -39,6 +39,7 @@ use crate::{ /// The ALU operation specifier 'Xi' determines the ALU operation by decoding 5 bits from the group /// decode rom, opcode, and optionally modrm. We don't bother decoding Xi. Instead, Xi is stored /// in the precalculated decode table. +#[derive(Copy, Clone, Debug)] pub enum Xi { ADD, ADC, diff --git a/core/src/cpu_vx0/biu.rs b/core/src/cpu_vx0/biu.rs index 4e841ce0..924fa064 100644 --- a/core/src/cpu_vx0/biu.rs +++ b/core/src/cpu_vx0/biu.rs @@ -465,7 +465,7 @@ impl NecVx0 { //validate_write_u8!(self, addr, (self.data_bus & 0x00FF) as u8); } - pub fn biu_io_read_u16(&mut self, addr: u16, flag: ReadWriteFlag) -> u16 { + pub fn biu_io_read_u16(&mut self, addr: u16) -> u16 { let mut word; self.biu_bus_begin( @@ -491,10 +491,7 @@ impl NecVx0 { false, ); - match flag { - ReadWriteFlag::Normal => self.biu_bus_wait_finish(), - ReadWriteFlag::RNI => self.biu_bus_wait_until_tx(), - }; + let _cycles = self.biu_bus_wait_finish(); word |= (self.data_bus & 0x00FF) << 8; diff --git a/core/src/cpu_vx0/cpu.rs b/core/src/cpu_vx0/cpu.rs index 97199b07..35fb903a 100644 --- a/core/src/cpu_vx0/cpu.rs +++ b/core/src/cpu_vx0/cpu.rs @@ -519,7 +519,7 @@ impl Cpu for NecVx0 { self.random_grp_instruction(opcode, extension_list) } - fn random_inst_from_opcodes(&mut self, opcode_list: &[u8]) { - self.random_inst_from_opcodes(opcode_list); + fn random_inst_from_opcodes(&mut self, opcode_list: &[u8], prefix: Option) { + self.random_inst_from_opcodes(opcode_list, prefix); } } diff --git a/core/src/cpu_vx0/decode.rs b/core/src/cpu_vx0/decode.rs index 32c03e48..7fbe34fd 100644 --- a/core/src/cpu_vx0/decode.rs +++ b/core/src/cpu_vx0/decode.rs @@ -44,11 +44,12 @@ use crate::{ cpu_vx0::{alu::Xi, gdr::GdrEntry}, cpu_common::{AddressingMode, Instruction}, }; -use crate::cpu_common::{Mnemonic, Segment, OperandType}; +use crate::cpu_common::{Mnemonic, Segment, OperandType, OPCODE_PREFIX_ES_OVERRIDE, OPCODE_PREFIX_CS_OVERRIDE, OPCODE_PREFIX_SS_OVERRIDE, OPCODE_PREFIX_DS_OVERRIDE, OPCODE_PREFIX_LOCK, OPCODE_PREFIX_REP1, OPCODE_PREFIX_REP2, OPCODE_PREFIX_REP3, OPCODE_PREFIX_REP4, OPCODE_PREFIX_0F}; use crate::cpu_common::operands::OperandSize; -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, Default, PartialEq)] pub enum OperandTemplate { + #[default] NoTemplate, NoOperand, ModRM8, @@ -107,6 +108,7 @@ impl Display for InstructionDecodeError { } } +#[derive(Copy, Clone, Default)] pub struct InstTemplate { pub grp: u8, pub gdr: GdrEntry, @@ -116,10 +118,29 @@ pub struct InstTemplate { pub operand1: OperandTemplate, pub operand2: OperandTemplate, } +impl InstTemplate { + const fn constdefault() -> Self { + Self { + grp: 0, + gdr: GdrEntry(0), + mc: 0, + xi: None, + mnemonic: Mnemonic::Invalid, + operand1: Ot::NoOperand, + operand2: Ot::NoOperand, + } + } +} +macro_rules! inst_skip { + ($init:ident, $ct:literal) => { + $init.idx += $ct; + }; + +} macro_rules! inst { - ($op:literal, $grp:literal, $gdr:literal, $mc:literal, $xi:ident, $m:ident, $o1:expr, $o2:expr) => { - InstTemplate { + ($opcode:literal, $init:ident, $grp:literal, $gdr:literal, $mc:literal, $xi:ident, $m:ident, $o1:expr, $o2:expr) => { + $init.table[$init.idx] = InstTemplate { grp: $grp, gdr: GdrEntry($gdr), mc: $mc, @@ -127,10 +148,11 @@ macro_rules! inst { mnemonic: Mnemonic::$m, operand1: $o1, operand2: $o2, - } + }; + $init.idx += 1; }; - ($op:literal, $grp:literal, $gdr:literal, $mc:literal, $m:ident, $o1:expr, $o2:expr) => { - InstTemplate { + ($opcode:literal, $init:ident, $grp:literal, $gdr:literal, $mc:literal, $m:ident, $o1:expr, $o2:expr) => { + $init.table[$init.idx] = InstTemplate { grp: $grp, gdr: GdrEntry($gdr), mc: $mc, @@ -138,395 +160,420 @@ macro_rules! inst { mnemonic: Mnemonic::$m, operand1: $o1, operand2: $o2, - } + }; + $init.idx += 1; }; } +pub const REGULAR_OPS_LEN: usize = 368; +pub const TOTAL_OPS_LEN: usize = REGULAR_OPS_LEN + 256; + +pub struct TableInitializer { + pub idx: usize, + pub table: [InstTemplate; TOTAL_OPS_LEN], +} + +impl TableInitializer { + const fn new() -> Self { + Self { + idx: 0, + table: [InstTemplate::constdefault(); TOTAL_OPS_LEN], + } + } +} + #[rustfmt::skip] -pub const DECODE: [InstTemplate; 368] = [ - inst!( 0x00, 0, 0b0100101000000000, 0x008, ADD , ADD, Ot::ModRM8, Ot::Register8), - inst!( 0x01, 0, 0b0100101000000000, 0x008, ADD , ADD, Ot::ModRM16, Ot::Register16), - inst!( 0x02, 0, 0b0100101000000000, 0x008, ADD , ADD, Ot::Register8, Ot::ModRM8), - inst!( 0x03, 0, 0b0100101000000000, 0x008, ADD , ADD, Ot::Register16, Ot::ModRM16), - inst!( 0x04, 0, 0b0100100010010010, 0x018, ADD , ADD, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), - inst!( 0x05, 0, 0b0100100010010010, 0x018, ADD , ADD, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), - inst!( 0x06, 0, 0b0100000000110010, 0x02c, PUSH, Ot::FixedRegister16(Register16::ES), Ot::NoOperand), - inst!( 0x07, 0, 0b0100000000110010, 0x038, POP, Ot::FixedRegister16(Register16::ES), Ot::NoOperand), - inst!( 0x08, 0, 0b0100101000000000, 0x008, OR , OR, Ot::ModRM8, Ot::Register8), - inst!( 0x09, 0, 0b0100101000000000, 0x008, OR , OR, Ot::ModRM16, Ot::Register16), - inst!( 0x0A, 0, 0b0100101000000000, 0x008, OR , OR, Ot::Register8, Ot::ModRM8), - inst!( 0x0B, 0, 0b0100101000000000, 0x008, OR , OR, Ot::Register16, Ot::ModRM16), - inst!( 0x0C, 0, 0b0100100010010010, 0x018, OR , OR, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), - inst!( 0x0D, 0, 0b0100100010010010, 0x018, OR , OR, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), - inst!( 0x0E, 0, 0b0100000000110010, 0x02c, PUSH, Ot::FixedRegister16(Register16::CS), Ot::NoOperand), - inst!( 0x0F, 0, 0b0100000000110010, 0x038, POP, Ot::FixedRegister16(Register16::CS), Ot::NoOperand), - inst!( 0x10, 0, 0b0100101000000000, 0x008, ADC , ADC, Ot::ModRM8, Ot::Register8), - inst!( 0x11, 0, 0b0100101000000000, 0x008, ADC , ADC, Ot::ModRM16, Ot::Register16), - inst!( 0x12, 0, 0b0100101000000000, 0x008, ADC , ADC, Ot::Register8, Ot::ModRM8), - inst!( 0x13, 0, 0b0100101000000000, 0x008, ADC , ADC, Ot::Register16, Ot::ModRM16), - inst!( 0x14, 0, 0b0100100010010010, 0x018, ADC , ADC, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), - inst!( 0x15, 0, 0b0100100010010010, 0x018, ADC , ADC, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), - inst!( 0x16, 0, 0b0100000000110010, 0x02c, PUSH, Ot::FixedRegister16(Register16::SS), Ot::NoOperand), - inst!( 0x17, 0, 0b0100000000110010, 0x038, POP, Ot::FixedRegister16(Register16::SS), Ot::NoOperand), - inst!( 0x18, 0, 0b0100101000000000, 0x008, SBB , SBB, Ot::ModRM8, Ot::Register8), - inst!( 0x19, 0, 0b0100101000000000, 0x008, SBB , SBB, Ot::ModRM16, Ot::Register16), - inst!( 0x1A, 0, 0b0100101000000000, 0x008, SBB , SBB, Ot::Register8, Ot::ModRM8), - inst!( 0x1B, 0, 0b0100101000000000, 0x008, SBB , SBB, Ot::Register16, Ot::ModRM16), - inst!( 0x1C, 0, 0b0100100010010010, 0x018, SBB , SBB, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), - inst!( 0x1D, 0, 0b0100100010010010, 0x018, SBB , SBB, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), - inst!( 0x1E, 0, 0b0100000000110010, 0x02c, PUSH, Ot::FixedRegister16(Register16::DS), Ot::NoOperand), - inst!( 0x1F, 0, 0b0100000000110010, 0x038, POP, Ot::FixedRegister16(Register16::DS), Ot::NoOperand), - inst!( 0x20, 0, 0b0100101000000000, 0x008, AND , AND, Ot::ModRM8, Ot::Register8), - inst!( 0x21, 0, 0b0100101000000000, 0x008, AND , AND, Ot::ModRM16, Ot::Register16), - inst!( 0x22, 0, 0b0100101000000000, 0x008, AND , AND, Ot::Register8, Ot::ModRM8), - inst!( 0x23, 0, 0b0100101000000000, 0x008, AND , AND, Ot::Register16, Ot::ModRM16), - inst!( 0x24, 0, 0b0100100010010010, 0x018, AND , AND, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), - inst!( 0x25, 0, 0b0100100010010010, 0x018, AND , AND, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), - inst!( 0x26, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand), - inst!( 0x27, 0, 0b0101000000110010, 0x144, DAA , DAA, Ot::NoOperand, Ot::NoOperand), - inst!( 0x28, 0, 0b0100101000000000, 0x008, SUB , SUB, Ot::ModRM8, Ot::Register8), - inst!( 0x29, 0, 0b0100101000000000, 0x008, SUB , SUB, Ot::ModRM16, Ot::Register16), - inst!( 0x2A, 0, 0b0100101000000000, 0x008, SUB , SUB, Ot::Register8, Ot::ModRM8), - inst!( 0x2B, 0, 0b0100101000000000, 0x008, SUB , SUB, Ot::Register16, Ot::ModRM16), - inst!( 0x2C, 0, 0b0100100010010010, 0x018, SUB , SUB, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), - inst!( 0x2D, 0, 0b0100100010010010, 0x018, SUB , SUB, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), - inst!( 0x2E, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand), - inst!( 0x2F, 0, 0b0101000000110010, 0x144, DAS , DAS, Ot::NoOperand, Ot::NoOperand), - inst!( 0x30, 0, 0b0100101000000000, 0x008, XOR , XOR, Ot::ModRM8, Ot::Register8), - inst!( 0x31, 0, 0b0100101000000000, 0x008, XOR , XOR, Ot::ModRM16, Ot::Register16), - inst!( 0x32, 0, 0b0100101000000000, 0x008, XOR , XOR, Ot::Register8, Ot::ModRM8), - inst!( 0x33, 0, 0b0100101000000000, 0x008, XOR , XOR, Ot::Register16, Ot::ModRM16), - inst!( 0x34, 0, 0b0100100010010010, 0x018, XOR , XOR, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), - inst!( 0x35, 0, 0b0100100010010010, 0x018, XOR , XOR, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), - inst!( 0x36, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand), - inst!( 0x37, 0, 0b0101000000110010, 0x148, AAA , AAA, Ot::NoOperand, Ot::NoOperand), - inst!( 0x38, 0, 0b0100101000000000, 0x008, CMP , CMP, Ot::ModRM8, Ot::Register8), - inst!( 0x39, 0, 0b0100101000000000, 0x008, CMP , CMP, Ot::ModRM16, Ot::Register16), - inst!( 0x3A, 0, 0b0100101000000000, 0x008, CMP , CMP, Ot::Register8, Ot::ModRM8), - inst!( 0x3B, 0, 0b0100101000000000, 0x008, CMP , CMP, Ot::Register16, Ot::ModRM16), - inst!( 0x3C, 0, 0b0100100010010010, 0x018, CMP , CMP, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), - inst!( 0x3D, 0, 0b0100100010010010, 0x018, CMP , CMP, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), - inst!( 0x3E, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand), - inst!( 0x3F, 0, 0b0101000000110010, 0x148, AAS , AAS, Ot::NoOperand, Ot::NoOperand), - inst!( 0x40, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x41, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x42, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x43, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x44, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x45, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x46, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x47, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x48, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x49, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x4A, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x4B, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x4C, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x4D, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x4E, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x4F, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x50, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x51, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x52, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x53, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x54, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x55, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x56, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x57, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x58, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x59, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x5A, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x5B, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x5C, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x5D, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x5E, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x5F, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand), - inst!( 0x60, 0, 0b0000000000010000, 0x0e8, PUSHA, Ot::NoOperand, Ot::NoOperand), - inst!( 0x61, 0, 0b0000000000010000, 0x0e8, POPA, Ot::NoOperand, Ot::NoOperand), - inst!( 0x62, 0, 0b0000000000011000, 0x0e8, BOUND, Ot::Register16, Ot::ModRM16), - inst!( 0x63, 0, 0b0000000000011000, 0x0e8, BOUND, Ot::Register16, Ot::ModRM16), - inst!( 0x64, 0, 0b0000000000010000, 0x0e8, Prefix, Ot::NoOperand, Ot::NoOperand), - inst!( 0x65, 0, 0b0000000000010000, 0x0e8, Prefix, Ot::NoOperand, Ot::NoOperand), - inst!( 0x66, 0, 0b0000000000000000, 0x0e8, ESC, Ot::NoOperand, Ot::NoOperand), - inst!( 0x67, 0, 0b0000000000000000, 0x0e8, ESC, Ot::NoOperand, Ot::NoOperand), - inst!( 0x68, 0, 0b0000000000000000, 0x0e8, PUSH, Ot::Immediate16, Ot::NoOperand), - inst!( 0x69, 0, 0b0000000000000000, 0x0e8, IMUL, Ot::Register16, Ot::ModRM16), - inst!( 0x6A, 0, 0b0000000000000000, 0x0e8, PUSH, Ot::Immediate8, Ot::NoOperand), - inst!( 0x6B, 0, 0b0000000000000000, 0x0e8, IMUL, Ot::Register16, Ot::ModRM16), - inst!( 0x6C, 0, 0b0000000000000000, 0x0e8, INS, Ot::NoOperand, Ot::NoOperand), - inst!( 0x6D, 0, 0b0000000000000000, 0x0e8, INS, Ot::NoOperand, Ot::NoOperand), - inst!( 0x6E, 0, 0b0000000000000000, 0x0e8, OUTS, Ot::NoOperand, Ot::NoOperand), - inst!( 0x6F, 0, 0b0000000000000000, 0x0e8, OUTS, Ot::NoOperand, Ot::NoOperand), - inst!( 0x70, 0, 0b0000000000110010, 0x0e8, JO, Ot::Relative8, Ot::NoOperand), - inst!( 0x71, 0, 0b0000000000110010, 0x0e8, JNO, Ot::Relative8, Ot::NoOperand), - inst!( 0x72, 0, 0b0000000000110010, 0x0e8, JB, Ot::Relative8, Ot::NoOperand), - inst!( 0x73, 0, 0b0000000000110010, 0x0e8, JNB, Ot::Relative8, Ot::NoOperand), - inst!( 0x74, 0, 0b0000000000110010, 0x0e8, JZ, Ot::Relative8, Ot::NoOperand), - inst!( 0x75, 0, 0b0000000000110010, 0x0e8, JNZ, Ot::Relative8, Ot::NoOperand), - inst!( 0x76, 0, 0b0000000000110010, 0x0e8, JBE, Ot::Relative8, Ot::NoOperand), - inst!( 0x77, 0, 0b0000000000110010, 0x0e8, JNBE, Ot::Relative8, Ot::NoOperand), - inst!( 0x78, 0, 0b0000000000110010, 0x0e8, JS, Ot::Relative8, Ot::NoOperand), - inst!( 0x79, 0, 0b0000000000110010, 0x0e8, JNS, Ot::Relative8, Ot::NoOperand), - inst!( 0x7A, 0, 0b0000000000110010, 0x0e8, JP, Ot::Relative8, Ot::NoOperand), - inst!( 0x7B, 0, 0b0000000000110010, 0x0e8, JNP, Ot::Relative8, Ot::NoOperand), - inst!( 0x7C, 0, 0b0000000000110010, 0x0e8, JL, Ot::Relative8, Ot::NoOperand), - inst!( 0x7D, 0, 0b0000000000110010, 0x0e8, JNL, Ot::Relative8, Ot::NoOperand), - inst!( 0x7E, 0, 0b0000000000110010, 0x0e8, JLE, Ot::Relative8, Ot::NoOperand), - inst!( 0x7F, 0, 0b0000000000110010, 0x0e8, JNLE, Ot::Relative8, Ot::NoOperand), - inst!( 0x80, 1, 0b0110100000000000, 0x00c, ADD , Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0x81, 2, 0b0110100000000000, 0x00c, CMP , Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0x82, 3, 0b0110100000000000, 0x00c, ADD , Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0x83, 4, 0b0110100000000000, 0x00c, CMP , Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0x84, 0, 0b0110100000000000, 0x094, TEST, Ot::ModRM8, Ot::Register8), - inst!( 0x85, 0, 0b0110100000000000, 0x094, TEST, Ot::ModRM16, Ot::Register16), - inst!( 0x86, 0, 0b0110100000000000, 0x0a4, XCHG, Ot::Register8, Ot::ModRM8), - inst!( 0x87, 0, 0b0110100000000000, 0x0a4, XCHG, Ot::Register16, Ot::ModRM16), - inst!( 0x88, 0, 0b0100101000100010, 0x000, MOV, Ot::ModRM8, Ot::Register8), - inst!( 0x89, 0, 0b0100101000100010, 0x000, MOV, Ot::ModRM16, Ot::Register16), - inst!( 0x8A, 0, 0b0100101000100000, 0x000, MOV, Ot::Register8, Ot::ModRM8), - inst!( 0x8B, 0, 0b0100101000100000, 0x000, MOV, Ot::Register16, Ot::ModRM16), - inst!( 0x8C, 0, 0b0100001100100010, 0x0ec, MOV, Ot::ModRM16, Ot::SegmentRegister), - inst!( 0x8D, 0, 0b0100000000100010, 0x004, LEA, Ot::Register16, Ot::ModRM16), - inst!( 0x8E, 0, 0b0100001100100000, 0x0ec, MOV, Ot::SegmentRegister, Ot::ModRM16), - inst!( 0x8F, 0, 0b0100000000100010, 0x040, POP, Ot::ModRM16, Ot::NoOperand), - inst!( 0x90, 0, 0b0100000000110010, 0x084, NOP, Ot::NoOperand, Ot::NoOperand), - inst!( 0x91, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)), - inst!( 0x92, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)), - inst!( 0x93, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)), - inst!( 0x94, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)), - inst!( 0x95, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)), - inst!( 0x96, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)), - inst!( 0x97, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)), - inst!( 0x98, 0, 0b0100000000110010, 0x054, CBW, Ot::NoOperand, Ot::NoOperand), - inst!( 0x99, 0, 0b0100000000110010, 0x058, CWD, Ot::NoOperand, Ot::NoOperand), - inst!( 0x9A, 0, 0b0100000000110010, 0x070, CALLF, Ot::FarAddress, Ot::NoOperand), - inst!( 0x9B, 0, 0b0100000000110010, 0x0f8, WAIT, Ot::NoOperand, Ot::NoOperand), - inst!( 0x9C, 0, 0b0100000000110010, 0x030, PUSHF, Ot::NoOperand, Ot::NoOperand), - inst!( 0x9D, 0, 0b0100000000110010, 0x03c, POPF, Ot::NoOperand, Ot::NoOperand), - inst!( 0x9E, 0, 0b0100000000110010, 0x100, SAHF, Ot::NoOperand, Ot::NoOperand), - inst!( 0x9F, 0, 0b0100000000110010, 0x104, LAHF, Ot::NoOperand, Ot::NoOperand), - inst!( 0xA0, 0, 0b0100100010110010, 0x060, MOV, Ot::FixedRegister8(Register8::AL), Ot::Offset8), - inst!( 0xA1, 0, 0b0100100010110010, 0x060, MOV, Ot::FixedRegister16(Register16::AX), Ot::Offset16), - inst!( 0xA2, 0, 0b0100100010110010, 0x064, MOV, Ot::Offset8, Ot::FixedRegister8(Register8::AL)), - inst!( 0xA3, 0, 0b0100100010110010, 0x064, MOV, Ot::Offset16, Ot::FixedRegister16(Register16::AX)), - inst!( 0xA4, 0, 0b0100100010110010, 0x12c, MOVSB, Ot::NoOperand, Ot::NoOperand), - inst!( 0xA5, 0, 0b0100100010110010, 0x12c, MOVSW, Ot::NoOperand, Ot::NoOperand), - inst!( 0xA6, 0, 0b0100100010110010, 0x120, CMPSB, Ot::NoOperand, Ot::NoOperand), - inst!( 0xA7, 0, 0b0100100010110010, 0x120, CMPSW, Ot::NoOperand, Ot::NoOperand), - inst!( 0xA8, 0, 0b0100100010110010, 0x09C, TEST, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), - inst!( 0xA9, 0, 0b0100100010110010, 0x09C, TEST, Ot::FixedRegister16(Register16::AX), Ot::Immediate16), - inst!( 0xAA, 0, 0b0100100010110010, 0x11c, STOSB, Ot::NoOperand, Ot::NoOperand), - inst!( 0xAB, 0, 0b0100100010110010, 0x11c, STOSW, Ot::NoOperand, Ot::NoOperand), - inst!( 0xAC, 0, 0b0100100010110010, 0x12c, LODSB, Ot::NoOperand, Ot::NoOperand), - inst!( 0xAD, 0, 0b0100100010110010, 0x12c, LODSW, Ot::NoOperand, Ot::NoOperand), - inst!( 0xAE, 0, 0b0100100010110010, 0x120, SCASB, Ot::NoOperand, Ot::NoOperand), - inst!( 0xAF, 0, 0b0100100010110010, 0x120, SCASW, Ot::NoOperand, Ot::NoOperand), - inst!( 0xB0, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), - inst!( 0xB1, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), - inst!( 0xB2, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), - inst!( 0xB3, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), - inst!( 0xB4, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), - inst!( 0xB5, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), - inst!( 0xB6, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), - inst!( 0xB7, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8), - inst!( 0xB8, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), - inst!( 0xB9, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), - inst!( 0xBA, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), - inst!( 0xBB, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), - inst!( 0xBC, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), - inst!( 0xBD, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), - inst!( 0xBE, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), - inst!( 0xBF, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16), - inst!( 0xC0, 5, 0b0100000000110000, 0x0cc, Group, Ot::Immediate16, Ot::NoOperand), - inst!( 0xC1, 6, 0b0100000000110000, 0x0bc, Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0xC2, 0, 0b0100000000110000, 0x0cc, RETN, Ot::Immediate16, Ot::NoOperand), - inst!( 0xC3, 0, 0b0100000000110000, 0x0bc, RETN, Ot::NoOperand, Ot::NoOperand), - inst!( 0xC4, 0, 0b0100000000100000, 0x0f0, LES, Ot::Register16, Ot::ModRM16), - inst!( 0xC5, 0, 0b0100000000100000, 0x0f4, LDS, Ot::Register16, Ot::ModRM16), - inst!( 0xC6, 0, 0b0100100000100010, 0x014, MOV, Ot::ModRM8, Ot::Immediate8), - inst!( 0xC7, 0, 0b0100100000100010, 0x014, MOV, Ot::ModRM16, Ot::Immediate16), - inst!( 0xC8, 0, 0b0100000000110000, 0x0cc, RETF, Ot::Immediate16, Ot::NoOperand), - inst!( 0xC9, 0, 0b0100000000110000, 0x0c0, RETF, Ot::NoOperand, Ot::NoOperand), - inst!( 0xCA, 0, 0b0100000000110000, 0x0cc, RETF, Ot::Immediate16, Ot::NoOperand), - inst!( 0xCB, 0, 0b0100000000110000, 0x0c0, RETF, Ot::NoOperand, Ot::NoOperand), - inst!( 0xCC, 0, 0b0100000000110000, 0x1b0, INT3, Ot::NoOperand, Ot::NoOperand), - inst!( 0xCD, 0, 0b0100000000110000, 0x1a8, INT, Ot::Immediate8, Ot::NoOperand), - inst!( 0xCE, 0, 0b0100000000110000, 0x1ac, INTO, Ot::NoOperand, Ot::NoOperand), - inst!( 0xCF, 0, 0b0100000000110000, 0x0c8, IRET, Ot::NoOperand, Ot::NoOperand), - inst!( 0xD0, 7, 0b0100100000000000, 0x088, ROL , Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0xD1, 8, 0b0100100000000000, 0x088, SAR , Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0xD2, 9, 0b0100100000000000, 0x08c, ROL , Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0xD3, 10, 0b0100100000000000, 0x08c, SAR , Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0xD4, 0, 0b0101000000110000, 0x174, AAM, Ot::Immediate8, Ot::NoOperand), - inst!( 0xD5, 0, 0b0101000000110000, 0x170, AAD, Ot::Immediate8, Ot::NoOperand), - inst!( 0xD6, 0, 0b0101000000110000, 0x0a0, XLAT, Ot::NoOperand, Ot::NoOperand), - inst!( 0xD7, 0, 0b0101000000110000, 0x10c, XLAT, Ot::NoOperand, Ot::NoOperand), - inst!( 0xD8, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), - inst!( 0xD9, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), - inst!( 0xDA, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), - inst!( 0xDB, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), - inst!( 0xDC, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), - inst!( 0xDD, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), - inst!( 0xDE, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), - inst!( 0xDF, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand), - inst!( 0xE0, 0, 0b0110000000110000, 0x138, LOOPNE, Ot::Relative8, Ot::NoOperand), - inst!( 0xE1, 0, 0b0110000000110000, 0x138, LOOPE, Ot::Relative8, Ot::NoOperand), - inst!( 0xE2, 0, 0b0110000000110000, 0x140, LOOP, Ot::Relative8, Ot::NoOperand), - inst!( 0xE3, 0, 0b0110000000110000, 0x134, JCXZ, Ot::Relative8, Ot::NoOperand), - inst!( 0xE4, 0, 0b0100100010110011, 0x0ac, IN, Ot::FixedRegister8(Register8::AL), Ot::Immediate8), - inst!( 0xE5, 0, 0b0100100010110011, 0x0ac, IN, Ot::FixedRegister16(Register16::AX), Ot::Immediate8), - inst!( 0xE6, 0, 0b0100100010110011, 0x0b0, OUT, Ot::Immediate8, Ot::FixedRegister8(Register8::AL)), - inst!( 0xE7, 0, 0b0100100010110011, 0x0b0, OUT, Ot::Immediate8, Ot::FixedRegister16(Register16::AX)), - inst!( 0xE8, 0, 0b0110000000110000, 0x07c, CALL, Ot::Relative16, Ot::NoOperand), - inst!( 0xE9, 0, 0b0110000000110000, 0x0d0, JMP, Ot::Relative16, Ot::NoOperand), - inst!( 0xEA, 0, 0b0110000000110000, 0x0e0, JMPF, Ot::FarAddress, Ot::NoOperand), - inst!( 0xEB, 0, 0b0110000000110000, 0x0d0, JMP, Ot::Relative8, Ot::NoOperand), - inst!( 0xEC, 0, 0b0100100010110011, 0x0b4, IN, Ot::FixedRegister8(Register8::AL), Ot::FixedRegister16(Register16::DX)), - inst!( 0xED, 0, 0b0100100010110011, 0x0b4, IN, Ot::FixedRegister16(Register16::AX), Ot::FixedRegister16(Register16::DX)), - inst!( 0xEE, 0, 0b0100100010110011, 0x0b8, OUT, Ot::FixedRegister16(Register16::DX), Ot::FixedRegister8(Register8::AL)), - inst!( 0xEF, 0, 0b0100100010110011, 0x0b8, OUT, Ot::FixedRegister16(Register16::DX), Ot::FixedRegister16(Register16::AX)), - inst!( 0xF0, 0, 0b0100010000111010, 0x1FF, LOCK, Ot::NoOperand, Ot::NoOperand), - inst!( 0xF1, 0, 0b0100010000111010, 0x1FF, LOCK, Ot::NoOperand, Ot::NoOperand), - inst!( 0xF2, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand), - inst!( 0xF3, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand), - inst!( 0xF4, 0, 0b0100010000110010, 0x1FF, HLT, Ot::NoOperand, Ot::NoOperand), - inst!( 0xF5, 0, 0b0100010000110010, 0x1FF, CMC, Ot::NoOperand, Ot::NoOperand), - inst!( 0xF6, 11, 0b0100100000100100, 0x098, Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0xF7, 12, 0b0100100000100100, 0x160, Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0xF8, 0, 0b0100010001110010, 0x1FF, CLC, Ot::NoOperand, Ot::NoOperand), - inst!( 0xF9, 0, 0b0100010001110010, 0x1FF, STC, Ot::NoOperand, Ot::NoOperand), - inst!( 0xFA, 0, 0b0100010001110010, 0x1FF, CLI, Ot::NoOperand, Ot::NoOperand), - inst!( 0xFB, 0, 0b0100010001110010, 0x1FF, STI, Ot::NoOperand, Ot::NoOperand), - inst!( 0xFC, 0, 0b0100010001110010, 0x1FF, CLD, Ot::NoOperand, Ot::NoOperand), - inst!( 0xFD, 0, 0b0100010001110010, 0x1FF, STD, Ot::NoOperand, Ot::NoOperand), - inst!( 0xFE, 13, 0b0000100000100100, 0x020, Group, Ot::NoOperand, Ot::NoOperand), - inst!( 0xFF, 14, 0b0000100000100100, 0x026, Group, Ot::NoOperand, Ot::NoOperand), +pub static DECODE: [InstTemplate; TOTAL_OPS_LEN] = { + let mut o: TableInitializer = TableInitializer::new(); + inst!( 0x00, o, 0, 0b0100101000000000, 0x008, ADD , ADD, Ot::ModRM8, Ot::Register8); + inst!( 0x01, o, 0, 0b0100101000000000, 0x008, ADD , ADD, Ot::ModRM16, Ot::Register16); + inst!( 0x02, o, 0, 0b0100101000000000, 0x008, ADD , ADD, Ot::Register8, Ot::ModRM8); + inst!( 0x03, o, 0, 0b0100101000000000, 0x008, ADD , ADD, Ot::Register16, Ot::ModRM16); + inst!( 0x04, o, 0, 0b0100100010010010, 0x018, ADD , ADD, Ot::FixedRegister8(Register8::AL), Ot::Immediate8); + inst!( 0x05, o, 0, 0b0100100010010010, 0x018, ADD , ADD, Ot::FixedRegister16(Register16::AX), Ot::Immediate16); + inst!( 0x06, o, 0, 0b0100000000110010, 0x02c, PUSH, Ot::FixedRegister16(Register16::ES), Ot::NoOperand); + inst!( 0x07, o, 0, 0b0100000000110010, 0x038, POP, Ot::FixedRegister16(Register16::ES), Ot::NoOperand); + inst!( 0x08, o, 0, 0b0100101000000000, 0x008, OR , OR, Ot::ModRM8, Ot::Register8); + inst!( 0x09, o, 0, 0b0100101000000000, 0x008, OR , OR, Ot::ModRM16, Ot::Register16); + inst!( 0x0A, o, 0, 0b0100101000000000, 0x008, OR , OR, Ot::Register8, Ot::ModRM8); + inst!( 0x0B, o, 0, 0b0100101000000000, 0x008, OR , OR, Ot::Register16, Ot::ModRM16); + inst!( 0x0C, o, 0, 0b0100100010010010, 0x018, OR , OR, Ot::FixedRegister8(Register8::AL), Ot::Immediate8); + inst!( 0x0D, o, 0, 0b0100100010010010, 0x018, OR , OR, Ot::FixedRegister16(Register16::AX), Ot::Immediate16); + inst!( 0x0E, o, 0, 0b0100000000110010, 0x02c, PUSH, Ot::FixedRegister16(Register16::CS), Ot::NoOperand); + inst!( 0x0F, o, 0, 0b0100000000110010, 0x038, POP, Ot::FixedRegister16(Register16::CS), Ot::NoOperand); + inst!( 0x10, o, 0, 0b0100101000000000, 0x008, ADC , ADC, Ot::ModRM8, Ot::Register8); + inst!( 0x11, o, 0, 0b0100101000000000, 0x008, ADC , ADC, Ot::ModRM16, Ot::Register16); + inst!( 0x12, o, 0, 0b0100101000000000, 0x008, ADC , ADC, Ot::Register8, Ot::ModRM8); + inst!( 0x13, o, 0, 0b0100101000000000, 0x008, ADC , ADC, Ot::Register16, Ot::ModRM16); + inst!( 0x14, o, 0, 0b0100100010010010, 0x018, ADC , ADC, Ot::FixedRegister8(Register8::AL), Ot::Immediate8); + inst!( 0x15, o, 0, 0b0100100010010010, 0x018, ADC , ADC, Ot::FixedRegister16(Register16::AX), Ot::Immediate16); + inst!( 0x16, o, 0, 0b0100000000110010, 0x02c, PUSH, Ot::FixedRegister16(Register16::SS), Ot::NoOperand); + inst!( 0x17, o, 0, 0b0100000000110010, 0x038, POP, Ot::FixedRegister16(Register16::SS), Ot::NoOperand); + inst!( 0x18, o, 0, 0b0100101000000000, 0x008, SBB , SBB, Ot::ModRM8, Ot::Register8); + inst!( 0x19, o, 0, 0b0100101000000000, 0x008, SBB , SBB, Ot::ModRM16, Ot::Register16); + inst!( 0x1A, o, 0, 0b0100101000000000, 0x008, SBB , SBB, Ot::Register8, Ot::ModRM8); + inst!( 0x1B, o, 0, 0b0100101000000000, 0x008, SBB , SBB, Ot::Register16, Ot::ModRM16); + inst!( 0x1C, o, 0, 0b0100100010010010, 0x018, SBB , SBB, Ot::FixedRegister8(Register8::AL), Ot::Immediate8); + inst!( 0x1D, o, 0, 0b0100100010010010, 0x018, SBB , SBB, Ot::FixedRegister16(Register16::AX), Ot::Immediate16); + inst!( 0x1E, o, 0, 0b0100000000110010, 0x02c, PUSH, Ot::FixedRegister16(Register16::DS), Ot::NoOperand); + inst!( 0x1F, o, 0, 0b0100000000110010, 0x038, POP, Ot::FixedRegister16(Register16::DS), Ot::NoOperand); + inst!( 0x20, o, 0, 0b0100101000000000, 0x008, AND , AND, Ot::ModRM8, Ot::Register8); + inst!( 0x21, o, 0, 0b0100101000000000, 0x008, AND , AND, Ot::ModRM16, Ot::Register16); + inst!( 0x22, o, 0, 0b0100101000000000, 0x008, AND , AND, Ot::Register8, Ot::ModRM8); + inst!( 0x23, o, 0, 0b0100101000000000, 0x008, AND , AND, Ot::Register16, Ot::ModRM16); + inst!( 0x24, o, 0, 0b0100100010010010, 0x018, AND , AND, Ot::FixedRegister8(Register8::AL), Ot::Immediate8); + inst!( 0x25, o, 0, 0b0100100010010010, 0x018, AND , AND, Ot::FixedRegister16(Register16::AX), Ot::Immediate16); + inst!( 0x26, o, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand); + inst!( 0x27, o, 0, 0b0101000000110010, 0x144, DAA , DAA, Ot::NoOperand, Ot::NoOperand); + inst!( 0x28, o, 0, 0b0100101000000000, 0x008, SUB , SUB, Ot::ModRM8, Ot::Register8); + inst!( 0x29, o, 0, 0b0100101000000000, 0x008, SUB , SUB, Ot::ModRM16, Ot::Register16); + inst!( 0x2A, o, 0, 0b0100101000000000, 0x008, SUB , SUB, Ot::Register8, Ot::ModRM8); + inst!( 0x2B, o, 0, 0b0100101000000000, 0x008, SUB , SUB, Ot::Register16, Ot::ModRM16); + inst!( 0x2C, o, 0, 0b0100100010010010, 0x018, SUB , SUB, Ot::FixedRegister8(Register8::AL), Ot::Immediate8); + inst!( 0x2D, o, 0, 0b0100100010010010, 0x018, SUB , SUB, Ot::FixedRegister16(Register16::AX), Ot::Immediate16); + inst!( 0x2E, o, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand); + inst!( 0x2F, o, 0, 0b0101000000110010, 0x144, DAS , DAS, Ot::NoOperand, Ot::NoOperand); + inst!( 0x30, o, 0, 0b0100101000000000, 0x008, XOR , XOR, Ot::ModRM8, Ot::Register8); + inst!( 0x31, o, 0, 0b0100101000000000, 0x008, XOR , XOR, Ot::ModRM16, Ot::Register16); + inst!( 0x32, o, 0, 0b0100101000000000, 0x008, XOR , XOR, Ot::Register8, Ot::ModRM8); + inst!( 0x33, o, 0, 0b0100101000000000, 0x008, XOR , XOR, Ot::Register16, Ot::ModRM16); + inst!( 0x34, o, 0, 0b0100100010010010, 0x018, XOR , XOR, Ot::FixedRegister8(Register8::AL), Ot::Immediate8); + inst!( 0x35, o, 0, 0b0100100010010010, 0x018, XOR , XOR, Ot::FixedRegister16(Register16::AX), Ot::Immediate16); + inst!( 0x36, o, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand); + inst!( 0x37, o, 0, 0b0101000000110010, 0x148, AAA , AAA, Ot::NoOperand, Ot::NoOperand); + inst!( 0x38, o, 0, 0b0100101000000000, 0x008, CMP , CMP, Ot::ModRM8, Ot::Register8); + inst!( 0x39, o, 0, 0b0100101000000000, 0x008, CMP , CMP, Ot::ModRM16, Ot::Register16); + inst!( 0x3A, o, 0, 0b0100101000000000, 0x008, CMP , CMP, Ot::Register8, Ot::ModRM8); + inst!( 0x3B, o, 0, 0b0100101000000000, 0x008, CMP , CMP, Ot::Register16, Ot::ModRM16); + inst!( 0x3C, o, 0, 0b0100100010010010, 0x018, CMP , CMP, Ot::FixedRegister8(Register8::AL), Ot::Immediate8); + inst!( 0x3D, o, 0, 0b0100100010010010, 0x018, CMP , CMP, Ot::FixedRegister16(Register16::AX), Ot::Immediate16); + inst!( 0x3E, o, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand); + inst!( 0x3F, o, 0, 0b0101000000110010, 0x148, AAS , AAS, Ot::NoOperand, Ot::NoOperand); + inst!( 0x40, o, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x41, o, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x42, o, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x43, o, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x44, o, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x45, o, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x46, o, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x47, o, 0, 0b0000000000110010, 0x17c, INC , INC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x48, o, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x49, o, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x4A, o, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x4B, o, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x4C, o, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x4D, o, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x4E, o, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x4F, o, 0, 0b0000000000110010, 0x17c, DEC , DEC, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x50, o, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x51, o, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x52, o, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x53, o, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x54, o, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x55, o, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x56, o, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x57, o, 0, 0b0000000000110010, 0x028, PUSH, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x58, o, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x59, o, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x5A, o, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x5B, o, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x5C, o, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x5D, o, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x5E, o, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x5F, o, 0, 0b0000000000110010, 0x034, POP, Ot::Register16Encoded, Ot::NoOperand); + inst!( 0x60, o, 0, 0b0000000000010000, 0x0e8, PUSHA, Ot::NoOperand, Ot::NoOperand); + inst!( 0x61, o, 0, 0b0000000000010000, 0x0e8, POPA, Ot::NoOperand, Ot::NoOperand); + inst!( 0x62, o, 0, 0b0000000000011000, 0x0e8, BOUND, Ot::Register16, Ot::ModRM16); + inst!( 0x63, o, 0, 0b0000000000011000, 0x0e8, BOUND, Ot::Register16, Ot::ModRM16); + inst!( 0x64, o, 0, 0b0000000000010000, 0x0e8, Prefix, Ot::NoOperand, Ot::NoOperand); + inst!( 0x65, o, 0, 0b0000000000010000, 0x0e8, Prefix, Ot::NoOperand, Ot::NoOperand); + inst!( 0x66, o, 0, 0b0000000000000000, 0x0e8, FPO2, Ot::ModRM16, Ot::NoOperand); + inst!( 0x67, o, 0, 0b0000000000000000, 0x0e8, FPO2, Ot::ModRM16, Ot::NoOperand); + inst!( 0x68, o, 0, 0b0000000000000000, 0x0e8, PUSH, Ot::Immediate16, Ot::NoOperand); + inst!( 0x69, o, 0, 0b0000000000000000, 0x0e8, IMUL, Ot::Register16, Ot::ModRM16); + inst!( 0x6A, o, 0, 0b0000000000000000, 0x0e8, PUSH, Ot::Immediate8, Ot::NoOperand); + inst!( 0x6B, o, 0, 0b0000000000000000, 0x0e8, IMUL, Ot::Register16, Ot::ModRM16); + inst!( 0x6C, o, 0, 0b0000000000000000, 0x0e8, INSB, Ot::ModRM8, Ot::NoOperand); + inst!( 0x6D, o, 0, 0b0000000000000000, 0x0e8, INSW, Ot::ModRM16, Ot::NoOperand); + inst!( 0x6E, o, 0, 0b0000000000000000, 0x0e8, OUTSB, Ot::ModRM8, Ot::NoOperand); + inst!( 0x6F, o, 0, 0b0000000000000000, 0x0e8, OUTSW, Ot::ModRM16, Ot::NoOperand); + inst!( 0x70, o, 0, 0b0000000000110010, 0x0e8, JO, Ot::Relative8, Ot::NoOperand); + inst!( 0x71, o, 0, 0b0000000000110010, 0x0e8, JNO, Ot::Relative8, Ot::NoOperand); + inst!( 0x72, o, 0, 0b0000000000110010, 0x0e8, JB, Ot::Relative8, Ot::NoOperand); + inst!( 0x73, o, 0, 0b0000000000110010, 0x0e8, JNB, Ot::Relative8, Ot::NoOperand); + inst!( 0x74, o, 0, 0b0000000000110010, 0x0e8, JZ, Ot::Relative8, Ot::NoOperand); + inst!( 0x75, o, 0, 0b0000000000110010, 0x0e8, JNZ, Ot::Relative8, Ot::NoOperand); + inst!( 0x76, o, 0, 0b0000000000110010, 0x0e8, JBE, Ot::Relative8, Ot::NoOperand); + inst!( 0x77, o, 0, 0b0000000000110010, 0x0e8, JNBE, Ot::Relative8, Ot::NoOperand); + inst!( 0x78, o, 0, 0b0000000000110010, 0x0e8, JS, Ot::Relative8, Ot::NoOperand); + inst!( 0x79, o, 0, 0b0000000000110010, 0x0e8, JNS, Ot::Relative8, Ot::NoOperand); + inst!( 0x7A, o, 0, 0b0000000000110010, 0x0e8, JP, Ot::Relative8, Ot::NoOperand); + inst!( 0x7B, o, 0, 0b0000000000110010, 0x0e8, JNP, Ot::Relative8, Ot::NoOperand); + inst!( 0x7C, o, 0, 0b0000000000110010, 0x0e8, JL, Ot::Relative8, Ot::NoOperand); + inst!( 0x7D, o, 0, 0b0000000000110010, 0x0e8, JNL, Ot::Relative8, Ot::NoOperand); + inst!( 0x7E, o, 0, 0b0000000000110010, 0x0e8, JLE, Ot::Relative8, Ot::NoOperand); + inst!( 0x7F, o, 0, 0b0000000000110010, 0x0e8, JNLE, Ot::Relative8, Ot::NoOperand); + inst!( 0x80, o, 1, 0b0110100000000000, 0x00c, ADD , Group, Ot::NoOperand, Ot::NoOperand); + inst!( 0x81, o, 2, 0b0110100000000000, 0x00c, CMP , Group, Ot::NoOperand, Ot::NoOperand); + inst!( 0x82, o, 3, 0b0110100000000000, 0x00c, ADD , Group, Ot::NoOperand, Ot::NoOperand); + inst!( 0x83, o, 4, 0b0110100000000000, 0x00c, CMP , Group, Ot::NoOperand, Ot::NoOperand); + inst!( 0x84, o, 0, 0b0110100000000000, 0x094, TEST, Ot::ModRM8, Ot::Register8); + inst!( 0x85, o, 0, 0b0110100000000000, 0x094, TEST, Ot::ModRM16, Ot::Register16); + inst!( 0x86, o, 0, 0b0110100000000000, 0x0a4, XCHG, Ot::Register8, Ot::ModRM8); + inst!( 0x87, o, 0, 0b0110100000000000, 0x0a4, XCHG, Ot::Register16, Ot::ModRM16); + inst!( 0x88, o, 0, 0b0100101000100010, 0x000, MOV, Ot::ModRM8, Ot::Register8); + inst!( 0x89, o, 0, 0b0100101000100010, 0x000, MOV, Ot::ModRM16, Ot::Register16); + inst!( 0x8A, o, 0, 0b0100101000100000, 0x000, MOV, Ot::Register8, Ot::ModRM8); + inst!( 0x8B, o, 0, 0b0100101000100000, 0x000, MOV, Ot::Register16, Ot::ModRM16); + inst!( 0x8C, o, 0, 0b0100001100100010, 0x0ec, MOV, Ot::ModRM16, Ot::SegmentRegister); + inst!( 0x8D, o, 0, 0b0100000000100010, 0x004, LEA, Ot::Register16, Ot::ModRM16); + inst!( 0x8E, o, 0, 0b0100001100100000, 0x0ec, MOV, Ot::SegmentRegister, Ot::ModRM16); + inst!( 0x8F, o, 0, 0b0100000000100010, 0x040, POP, Ot::ModRM16, Ot::NoOperand); + inst!( 0x90, o, 0, 0b0100000000110010, 0x084, NOP, Ot::NoOperand, Ot::NoOperand); + inst!( 0x91, o, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)); + inst!( 0x92, o, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)); + inst!( 0x93, o, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)); + inst!( 0x94, o, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)); + inst!( 0x95, o, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)); + inst!( 0x96, o, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)); + inst!( 0x97, o, 0, 0b0100000000110010, 0x084, XCHG, Ot::Register16Encoded, Ot::FixedRegister16(Register16::AX)); + inst!( 0x98, o, 0, 0b0100000000110010, 0x054, CBW, Ot::NoOperand, Ot::NoOperand); + inst!( 0x99, o, 0, 0b0100000000110010, 0x058, CWD, Ot::NoOperand, Ot::NoOperand); + inst!( 0x9A, o, 0, 0b0100000000110010, 0x070, CALLF, Ot::FarAddress, Ot::NoOperand); + inst!( 0x9B, o, 0, 0b0100000000110010, 0x0f8, WAIT, Ot::NoOperand, Ot::NoOperand); + inst!( 0x9C, o, 0, 0b0100000000110010, 0x030, PUSHF, Ot::NoOperand, Ot::NoOperand); + inst!( 0x9D, o, 0, 0b0100000000110010, 0x03c, POPF, Ot::NoOperand, Ot::NoOperand); + inst!( 0x9E, o, 0, 0b0100000000110010, 0x100, SAHF, Ot::NoOperand, Ot::NoOperand); + inst!( 0x9F, o, 0, 0b0100000000110010, 0x104, LAHF, Ot::NoOperand, Ot::NoOperand); + inst!( 0xA0, o, 0, 0b0100100010110010, 0x060, MOV, Ot::FixedRegister8(Register8::AL), Ot::Offset8); + inst!( 0xA1, o, 0, 0b0100100010110010, 0x060, MOV, Ot::FixedRegister16(Register16::AX), Ot::Offset16); + inst!( 0xA2, o, 0, 0b0100100010110010, 0x064, MOV, Ot::Offset8, Ot::FixedRegister8(Register8::AL)); + inst!( 0xA3, o, 0, 0b0100100010110010, 0x064, MOV, Ot::Offset16, Ot::FixedRegister16(Register16::AX)); + inst!( 0xA4, o, 0, 0b0100100010110010, 0x12c, MOVSB, Ot::NoOperand, Ot::NoOperand); + inst!( 0xA5, o, 0, 0b0100100010110010, 0x12c, MOVSW, Ot::NoOperand, Ot::NoOperand); + inst!( 0xA6, o, 0, 0b0100100010110010, 0x120, CMPSB, Ot::NoOperand, Ot::NoOperand); + inst!( 0xA7, o, 0, 0b0100100010110010, 0x120, CMPSW, Ot::NoOperand, Ot::NoOperand); + inst!( 0xA8, o, 0, 0b0100100010110010, 0x09C, TEST, Ot::FixedRegister8(Register8::AL), Ot::Immediate8); + inst!( 0xA9, o, 0, 0b0100100010110010, 0x09C, TEST, Ot::FixedRegister16(Register16::AX), Ot::Immediate16); + inst!( 0xAA, o, 0, 0b0100100010110010, 0x11c, STOSB, Ot::NoOperand, Ot::NoOperand); + inst!( 0xAB, o, 0, 0b0100100010110010, 0x11c, STOSW, Ot::NoOperand, Ot::NoOperand); + inst!( 0xAC, o, 0, 0b0100100010110010, 0x12c, LODSB, Ot::NoOperand, Ot::NoOperand); + inst!( 0xAD, o, 0, 0b0100100010110010, 0x12c, LODSW, Ot::NoOperand, Ot::NoOperand); + inst!( 0xAE, o, 0, 0b0100100010110010, 0x120, SCASB, Ot::NoOperand, Ot::NoOperand); + inst!( 0xAF, o, 0, 0b0100100010110010, 0x120, SCASW, Ot::NoOperand, Ot::NoOperand); + inst!( 0xB0, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8); + inst!( 0xB1, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8); + inst!( 0xB2, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8); + inst!( 0xB3, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8); + inst!( 0xB4, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8); + inst!( 0xB5, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8); + inst!( 0xB6, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8); + inst!( 0xB7, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register8Encoded, Ot::Immediate8); + inst!( 0xB8, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16); + inst!( 0xB9, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16); + inst!( 0xBA, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16); + inst!( 0xBB, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16); + inst!( 0xBC, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16); + inst!( 0xBD, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16); + inst!( 0xBE, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16); + inst!( 0xBF, o, 0, 0b0100000000110010, 0x01c, MOV, Ot::Register16Encoded, Ot::Immediate16); + inst!( 0xC0, o, 5, 0b0100000000110000, 0x0cc, Group, Ot::Immediate16, Ot::NoOperand); + inst!( 0xC1, o, 6, 0b0100000000110000, 0x0bc, Group, Ot::NoOperand, Ot::NoOperand); + inst!( 0xC2, o, 0, 0b0100000000110000, 0x0cc, RETN, Ot::Immediate16, Ot::NoOperand); + inst!( 0xC3, o, 0, 0b0100000000110000, 0x0bc, RETN, Ot::NoOperand, Ot::NoOperand); + inst!( 0xC4, o, 0, 0b0100000000100000, 0x0f0, LES, Ot::Register16, Ot::ModRM16); + inst!( 0xC5, o, 0, 0b0100000000100000, 0x0f4, LDS, Ot::Register16, Ot::ModRM16); + inst!( 0xC6, o, 0, 0b0100100000100010, 0x014, MOV, Ot::ModRM8, Ot::Immediate8); + inst!( 0xC7, o, 0, 0b0100100000100010, 0x014, MOV, Ot::ModRM16, Ot::Immediate16); + inst!( 0xC8, o, 0, 0b0100000000110000, 0x0cc, ENTER, Ot::Immediate16, Ot::Immediate8); + inst!( 0xC9, o, 0, 0b0100000000110000, 0x0c0, LEAVE, Ot::NoOperand, Ot::NoOperand); + inst!( 0xCA, o, 0, 0b0100000000110000, 0x0cc, RETF, Ot::Immediate16, Ot::NoOperand); + inst!( 0xCB, o, 0, 0b0100000000110000, 0x0c0, RETF, Ot::NoOperand, Ot::NoOperand); + inst!( 0xCC, o, 0, 0b0100000000110000, 0x1b0, INT3, Ot::NoOperand, Ot::NoOperand); + inst!( 0xCD, o, 0, 0b0100000000110000, 0x1a8, INT, Ot::Immediate8, Ot::NoOperand); + inst!( 0xCE, o, 0, 0b0100000000110000, 0x1ac, INTO, Ot::NoOperand, Ot::NoOperand); + inst!( 0xCF, o, 0, 0b0100000000110000, 0x0c8, IRET, Ot::NoOperand, Ot::NoOperand); + inst!( 0xD0, o, 7, 0b0100100000000000, 0x088, ROL , Group, Ot::NoOperand, Ot::NoOperand); + inst!( 0xD1, o, 8, 0b0100100000000000, 0x088, SAR , Group, Ot::NoOperand, Ot::NoOperand); + inst!( 0xD2, o, 9, 0b0100100000000000, 0x08c, ROL , Group, Ot::NoOperand, Ot::NoOperand); + inst!( 0xD3, o,10, 0b0100100000000000, 0x08c, SAR , Group, Ot::NoOperand, Ot::NoOperand); + inst!( 0xD4, o, 0, 0b0101000000110000, 0x174, AAM, Ot::Immediate8, Ot::NoOperand); + inst!( 0xD5, o, 0, 0b0101000000110000, 0x170, AAD, Ot::Immediate8, Ot::NoOperand); + inst!( 0xD6, o, 0, 0b0101000000110000, 0x0a0, XLAT, Ot::NoOperand, Ot::NoOperand); + inst!( 0xD7, o, 0, 0b0101000000110000, 0x10c, XLAT, Ot::NoOperand, Ot::NoOperand); + inst!( 0xD8, o, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand); + inst!( 0xD9, o, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand); + inst!( 0xDA, o, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand); + inst!( 0xDB, o, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand); + inst!( 0xDC, o, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand); + inst!( 0xDD, o, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand); + inst!( 0xDE, o, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand); + inst!( 0xDF, o, 0, 0b0100000000100000, 0x108, ESC, Ot::ModRM16, Ot::NoOperand); + inst!( 0xE0, o, 0, 0b0110000000110000, 0x138, LOOPNE, Ot::Relative8, Ot::NoOperand); + inst!( 0xE1, o, 0, 0b0110000000110000, 0x138, LOOPE, Ot::Relative8, Ot::NoOperand); + inst!( 0xE2, o, 0, 0b0110000000110000, 0x140, LOOP, Ot::Relative8, Ot::NoOperand); + inst!( 0xE3, o, 0, 0b0110000000110000, 0x134, JCXZ, Ot::Relative8, Ot::NoOperand); + inst!( 0xE4, o, 0, 0b0100100010110011, 0x0ac, IN, Ot::FixedRegister8(Register8::AL), Ot::Immediate8); + inst!( 0xE5, o, 0, 0b0100100010110011, 0x0ac, IN, Ot::FixedRegister16(Register16::AX), Ot::Immediate8); + inst!( 0xE6, o, 0, 0b0100100010110011, 0x0b0, OUT, Ot::Immediate8, Ot::FixedRegister8(Register8::AL)); + inst!( 0xE7, o, 0, 0b0100100010110011, 0x0b0, OUT, Ot::Immediate8, Ot::FixedRegister16(Register16::AX)); + inst!( 0xE8, o, 0, 0b0110000000110000, 0x07c, CALL, Ot::Relative16, Ot::NoOperand); + inst!( 0xE9, o, 0, 0b0110000000110000, 0x0d0, JMP, Ot::Relative16, Ot::NoOperand); + inst!( 0xEA, o, 0, 0b0110000000110000, 0x0e0, JMPF, Ot::FarAddress, Ot::NoOperand); + inst!( 0xEB, o, 0, 0b0110000000110000, 0x0d0, JMP, Ot::Relative8, Ot::NoOperand); + inst!( 0xEC, o, 0, 0b0100100010110011, 0x0b4, IN, Ot::FixedRegister8(Register8::AL), Ot::FixedRegister16(Register16::DX)); + inst!( 0xED, o, 0, 0b0100100010110011, 0x0b4, IN, Ot::FixedRegister16(Register16::AX), Ot::FixedRegister16(Register16::DX)); + inst!( 0xEE, o, 0, 0b0100100010110011, 0x0b8, OUT, Ot::FixedRegister16(Register16::DX), Ot::FixedRegister8(Register8::AL)); + inst!( 0xEF, o, 0, 0b0100100010110011, 0x0b8, OUT, Ot::FixedRegister16(Register16::DX), Ot::FixedRegister16(Register16::AX)); + inst!( 0xF0, o, 0, 0b0100010000111010, 0x1FF, LOCK, Ot::NoOperand, Ot::NoOperand); + inst!( 0xF1, o, 0, 0b0100010000111010, 0x1FF, LOCK, Ot::NoOperand, Ot::NoOperand); + inst!( 0xF2, o, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand); + inst!( 0xF3, o, 0, 0b0100010000111010, 0x1FF, Prefix, Ot::NoOperand, Ot::NoOperand); + inst!( 0xF4, o, 0, 0b0100010000110010, 0x1FF, HLT, Ot::NoOperand, Ot::NoOperand); + inst!( 0xF5, o, 0, 0b0100010000110010, 0x1FF, CMC, Ot::NoOperand, Ot::NoOperand); + inst!( 0xF6, o,11, 0b0100100000100100, 0x098, Group, Ot::NoOperand, Ot::NoOperand); + inst!( 0xF7, o,12, 0b0100100000100100, 0x160, Group, Ot::NoOperand, Ot::NoOperand); + inst!( 0xF8, o, 0, 0b0100010001110010, 0x1FF, CLC, Ot::NoOperand, Ot::NoOperand); + inst!( 0xF9, o, 0, 0b0100010001110010, 0x1FF, STC, Ot::NoOperand, Ot::NoOperand); + inst!( 0xFA, o, 0, 0b0100010001110010, 0x1FF, CLI, Ot::NoOperand, Ot::NoOperand); + inst!( 0xFB, o, 0, 0b0100010001110010, 0x1FF, STI, Ot::NoOperand, Ot::NoOperand); + inst!( 0xFC, o, 0, 0b0100010001110010, 0x1FF, CLD, Ot::NoOperand, Ot::NoOperand); + inst!( 0xFD, o, 0, 0b0100010001110010, 0x1FF, STD, Ot::NoOperand, Ot::NoOperand); + inst!( 0xFE, o,13, 0b0000100000100100, 0x020, Group, Ot::NoOperand, Ot::NoOperand); + inst!( 0xFF, o,14, 0b0000100000100100, 0x026, Group, Ot::NoOperand, Ot::NoOperand); // Group - inst!( 0x80, 1, 0b0110100000000000, 0x00c, ADD , ADD , Ot::ModRM8, Ot::Immediate8), - inst!( 0x80, 1, 0b0110100000000000, 0x00c, OR , OR , Ot::ModRM8, Ot::Immediate8), - inst!( 0x80, 1, 0b0110100000000000, 0x00c, ADC , ADC , Ot::ModRM8, Ot::Immediate8), - inst!( 0x80, 1, 0b0110100000000000, 0x00c, SBB , SBB , Ot::ModRM8, Ot::Immediate8), - inst!( 0x80, 1, 0b0110100000000000, 0x00c, AND , AND , Ot::ModRM8, Ot::Immediate8), - inst!( 0x80, 1, 0b0110100000000000, 0x00c, SUB , SUB , Ot::ModRM8, Ot::Immediate8), - inst!( 0x80, 1, 0b0110100000000000, 0x00c, XOR , XOR , Ot::ModRM8, Ot::Immediate8), - inst!( 0x80, 1, 0b0110100000000000, 0x00c, CMP , CMP , Ot::ModRM8, Ot::Immediate8), + inst!( 0x80, o, 1, 0b0110100000000000, 0x00c, ADD , ADD , Ot::ModRM8, Ot::Immediate8); + inst!( 0x80, o, 1, 0b0110100000000000, 0x00c, OR , OR , Ot::ModRM8, Ot::Immediate8); + inst!( 0x80, o, 1, 0b0110100000000000, 0x00c, ADC , ADC , Ot::ModRM8, Ot::Immediate8); + inst!( 0x80, o, 1, 0b0110100000000000, 0x00c, SBB , SBB , Ot::ModRM8, Ot::Immediate8); + inst!( 0x80, o, 1, 0b0110100000000000, 0x00c, AND , AND , Ot::ModRM8, Ot::Immediate8); + inst!( 0x80, o, 1, 0b0110100000000000, 0x00c, SUB , SUB , Ot::ModRM8, Ot::Immediate8); + inst!( 0x80, o, 1, 0b0110100000000000, 0x00c, XOR , XOR , Ot::ModRM8, Ot::Immediate8); + inst!( 0x80, o, 1, 0b0110100000000000, 0x00c, CMP , CMP , Ot::ModRM8, Ot::Immediate8); // Group - inst!( 0x81, 1, 0b0110100000000000, 0x00c, ADD , ADD , Ot::ModRM16, Ot::Immediate16), - inst!( 0x81, 1, 0b0110100000000000, 0x00c, OR , OR , Ot::ModRM16, Ot::Immediate16), - inst!( 0x81, 1, 0b0110100000000000, 0x00c, ADC , ADC , Ot::ModRM16, Ot::Immediate16), - inst!( 0x81, 1, 0b0110100000000000, 0x00c, SBB , SBB , Ot::ModRM16, Ot::Immediate16), - inst!( 0x81, 1, 0b0110100000000000, 0x00c, AND , AND , Ot::ModRM16, Ot::Immediate16), - inst!( 0x81, 1, 0b0110100000000000, 0x00c, SUB , SUB , Ot::ModRM16, Ot::Immediate16), - inst!( 0x81, 1, 0b0110100000000000, 0x00c, XOR , XOR , Ot::ModRM16, Ot::Immediate16), - inst!( 0x81, 1, 0b0110100000000000, 0x00c, CMP , CMP , Ot::ModRM16, Ot::Immediate16), + inst!( 0x81, o, 1, 0b0110100000000000, 0x00c, ADD , ADD , Ot::ModRM16, Ot::Immediate16); + inst!( 0x81, o, 1, 0b0110100000000000, 0x00c, OR , OR , Ot::ModRM16, Ot::Immediate16); + inst!( 0x81, o, 1, 0b0110100000000000, 0x00c, ADC , ADC , Ot::ModRM16, Ot::Immediate16); + inst!( 0x81, o, 1, 0b0110100000000000, 0x00c, SBB , SBB , Ot::ModRM16, Ot::Immediate16); + inst!( 0x81, o, 1, 0b0110100000000000, 0x00c, AND , AND , Ot::ModRM16, Ot::Immediate16); + inst!( 0x81, o, 1, 0b0110100000000000, 0x00c, SUB , SUB , Ot::ModRM16, Ot::Immediate16); + inst!( 0x81, o, 1, 0b0110100000000000, 0x00c, XOR , XOR , Ot::ModRM16, Ot::Immediate16); + inst!( 0x81, o, 1, 0b0110100000000000, 0x00c, CMP , CMP , Ot::ModRM16, Ot::Immediate16); + // Group, + inst!( 0x82, o, 1, 0b0110100000000000, 0x00c, ADD , ADD , Ot::ModRM8, Ot::Immediate8); + inst!( 0x82, o, 1, 0b0110100000000000, 0x00c, OR , OR , Ot::ModRM8, Ot::Immediate8); + inst!( 0x82, o, 1, 0b0110100000000000, 0x00c, ADC , ADC , Ot::ModRM8, Ot::Immediate8); + inst!( 0x82, o, 1, 0b0110100000000000, 0x00c, SBB , SBB , Ot::ModRM8, Ot::Immediate8); + inst!( 0x82, o, 1, 0b0110100000000000, 0x00c, AND , AND , Ot::ModRM8, Ot::Immediate8); + inst!( 0x82, o, 1, 0b0110100000000000, 0x00c, SUB , SUB , Ot::ModRM8, Ot::Immediate8); + inst!( 0x82, o, 1, 0b0110100000000000, 0x00c, XOR , XOR , Ot::ModRM8, Ot::Immediate8); + inst!( 0x82, o, 1, 0b0110100000000000, 0x00c, CMP , CMP , Ot::ModRM8, Ot::Immediate8); // Group - inst!( 0x82, 1, 0b0110100000000000, 0x00c, ADD , ADD , Ot::ModRM8, Ot::Immediate8), - inst!( 0x82, 1, 0b0110100000000000, 0x00c, OR , OR , Ot::ModRM8, Ot::Immediate8), - inst!( 0x82, 1, 0b0110100000000000, 0x00c, ADC , ADC , Ot::ModRM8, Ot::Immediate8), - inst!( 0x82, 1, 0b0110100000000000, 0x00c, SBB , SBB , Ot::ModRM8, Ot::Immediate8), - inst!( 0x82, 1, 0b0110100000000000, 0x00c, AND , AND , Ot::ModRM8, Ot::Immediate8), - inst!( 0x82, 1, 0b0110100000000000, 0x00c, SUB , SUB , Ot::ModRM8, Ot::Immediate8), - inst!( 0x82, 1, 0b0110100000000000, 0x00c, XOR , XOR , Ot::ModRM8, Ot::Immediate8), - inst!( 0x82, 1, 0b0110100000000000, 0x00c, CMP , CMP , Ot::ModRM8, Ot::Immediate8), + inst!( 0x83, o, 1, 0b0110100000000000, 0x00c, ADD , ADD , Ot::ModRM16, Ot::Immediate8SignExtended); + inst!( 0x83, o, 1, 0b0110100000000000, 0x00c, OR , OR , Ot::ModRM16, Ot::Immediate8SignExtended); + inst!( 0x83, o, 1, 0b0110100000000000, 0x00c, ADC , ADC , Ot::ModRM16, Ot::Immediate8SignExtended); + inst!( 0x83, o, 1, 0b0110100000000000, 0x00c, SBB , SBB , Ot::ModRM16, Ot::Immediate8SignExtended); + inst!( 0x83, o, 1, 0b0110100000000000, 0x00c, AND , AND , Ot::ModRM16, Ot::Immediate8SignExtended); + inst!( 0x83, o, 1, 0b0110100000000000, 0x00c, SUB , SUB , Ot::ModRM16, Ot::Immediate8SignExtended); + inst!( 0x83, o, 1, 0b0110100000000000, 0x00c, XOR , XOR , Ot::ModRM16, Ot::Immediate8SignExtended); + inst!( 0x83, o, 1, 0b0110100000000000, 0x00c, CMP , CMP , Ot::ModRM16, Ot::Immediate8SignExtended); // Group - inst!( 0x83, 1, 0b0110100000000000, 0x00c, ADD , ADD , Ot::ModRM16, Ot::Immediate8SignExtended), - inst!( 0x83, 1, 0b0110100000000000, 0x00c, OR , OR , Ot::ModRM16, Ot::Immediate8SignExtended), - inst!( 0x83, 1, 0b0110100000000000, 0x00c, ADC , ADC , Ot::ModRM16, Ot::Immediate8SignExtended), - inst!( 0x83, 1, 0b0110100000000000, 0x00c, SBB , SBB , Ot::ModRM16, Ot::Immediate8SignExtended), - inst!( 0x83, 1, 0b0110100000000000, 0x00c, AND , AND , Ot::ModRM16, Ot::Immediate8SignExtended), - inst!( 0x83, 1, 0b0110100000000000, 0x00c, SUB , SUB , Ot::ModRM16, Ot::Immediate8SignExtended), - inst!( 0x83, 1, 0b0110100000000000, 0x00c, XOR , XOR , Ot::ModRM16, Ot::Immediate8SignExtended), - inst!( 0x83, 1, 0b0110100000000000, 0x00c, CMP , CMP , Ot::ModRM16, Ot::Immediate8SignExtended), + inst!( 0xC0, o, 2, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM8, Ot::Immediate8); + inst!( 0xC0, o, 2, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM8, Ot::Immediate8); + inst!( 0xC0, o, 2, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM8, Ot::Immediate8); + inst!( 0xC0, o, 2, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM8, Ot::Immediate8); + inst!( 0xC0, o, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM8, Ot::Immediate8); + inst!( 0xC0, o, 2, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM8, Ot::Immediate8); + inst!( 0xC0, o, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM8, Ot::Immediate8); + inst!( 0xC0, o, 2, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM8, Ot::Immediate8); // Group - inst!( 0xC0, 2, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM8, Ot::Immediate8), - inst!( 0xC0, 2, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM8, Ot::Immediate8), - inst!( 0xC0, 2, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM8, Ot::Immediate8), - inst!( 0xC0, 2, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM8, Ot::Immediate8), - inst!( 0xC0, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM8, Ot::Immediate8), - inst!( 0xC0, 2, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM8, Ot::Immediate8), - inst!( 0xC0, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM8, Ot::Immediate8), - inst!( 0xC0, 2, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM8, Ot::Immediate8), + inst!( 0xC1, o, 2, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM16, Ot::Immediate8); + inst!( 0xC1, o, 2, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM16, Ot::Immediate8); + inst!( 0xC1, o, 2, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM16, Ot::Immediate8); + inst!( 0xC1, o, 2, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM16, Ot::Immediate8); + inst!( 0xC1, o, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM16, Ot::Immediate8); + inst!( 0xC1, o, 2, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM16, Ot::Immediate8); + inst!( 0xC1, o, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM16, Ot::Immediate8); + inst!( 0xC1, o, 2, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM16, Ot::Immediate8); // Group - inst!( 0xC1, 2, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM16, Ot::Immediate8), - inst!( 0xC1, 2, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM16, Ot::Immediate8), - inst!( 0xC1, 2, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM16, Ot::Immediate8), - inst!( 0xC1, 2, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM16, Ot::Immediate8), - inst!( 0xC1, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM16, Ot::Immediate8), - inst!( 0xC1, 2, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM16, Ot::Immediate8), - inst!( 0xC1, 2, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM16, Ot::Immediate8), - inst!( 0xC1, 2, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM16, Ot::Immediate8), + inst!( 0xD0, o, 3, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM8, Ot::NoOperand); + inst!( 0xD0, o, 3, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM8, Ot::NoOperand); + inst!( 0xD0, o, 3, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM8, Ot::NoOperand); + inst!( 0xD0, o, 3, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM8, Ot::NoOperand); + inst!( 0xD0, o, 3, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM8, Ot::NoOperand); + inst!( 0xD0, o, 3, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM8, Ot::NoOperand); + inst!( 0xD0, o, 3, 0b0100100000000000, 0x088, SETMO , SETMO, Ot::ModRM8, Ot::NoOperand); + inst!( 0xD0, o, 3, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM8, Ot::NoOperand); // Group - inst!( 0xD0, 3, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM8, Ot::NoOperand), - inst!( 0xD0, 3, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM8, Ot::NoOperand), - inst!( 0xD0, 3, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM8, Ot::NoOperand), - inst!( 0xD0, 3, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM8, Ot::NoOperand), - inst!( 0xD0, 3, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM8, Ot::NoOperand), - inst!( 0xD0, 3, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM8, Ot::NoOperand), - inst!( 0xD0, 3, 0b0100100000000000, 0x088, SETMO , SETMO, Ot::ModRM8, Ot::NoOperand), - inst!( 0xD0, 3, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM8, Ot::NoOperand), + inst!( 0xD1, o, 3, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM16, Ot::NoOperand); + inst!( 0xD1, o, 3, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM16, Ot::NoOperand); + inst!( 0xD1, o, 3, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM16, Ot::NoOperand); + inst!( 0xD1, o, 3, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM16, Ot::NoOperand); + inst!( 0xD1, o, 3, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM16, Ot::NoOperand); + inst!( 0xD1, o, 3, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM16, Ot::NoOperand); + inst!( 0xD1, o, 3, 0b0100100000000000, 0x088, SETMO , SETMO, Ot::ModRM16, Ot::NoOperand); + inst!( 0xD1, o, 3, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM16, Ot::NoOperand); // Group - inst!( 0xD1, 3, 0b0100100000000000, 0x088, ROL , ROL , Ot::ModRM16, Ot::NoOperand), - inst!( 0xD1, 3, 0b0100100000000000, 0x088, ROR , ROR , Ot::ModRM16, Ot::NoOperand), - inst!( 0xD1, 3, 0b0100100000000000, 0x088, RCL , RCL , Ot::ModRM16, Ot::NoOperand), - inst!( 0xD1, 3, 0b0100100000000000, 0x088, RCR , RCR , Ot::ModRM16, Ot::NoOperand), - inst!( 0xD1, 3, 0b0100100000000000, 0x088, SHL , SHL , Ot::ModRM16, Ot::NoOperand), - inst!( 0xD1, 3, 0b0100100000000000, 0x088, SHR , SHR , Ot::ModRM16, Ot::NoOperand), - inst!( 0xD1, 3, 0b0100100000000000, 0x088, SETMO , SETMO, Ot::ModRM16, Ot::NoOperand), - inst!( 0xD1, 3, 0b0100100000000000, 0x088, SAR , SAR , Ot::ModRM16, Ot::NoOperand), + inst!( 0xD2, o, 4, 0b0100100000000000, 0x08c, ROL , ROL , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)); + inst!( 0xD2, o, 4, 0b0100100000000000, 0x08c, ROR , ROR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)); + inst!( 0xD2, o, 4, 0b0100100000000000, 0x08c, RCL , RCL , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)); + inst!( 0xD2, o, 4, 0b0100100000000000, 0x08c, RCR , RCR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)); + inst!( 0xD2, o, 4, 0b0100100000000000, 0x08c, SHL , SHL , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)); + inst!( 0xD2, o, 4, 0b0100100000000000, 0x08c, SHR , SHR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)); + inst!( 0xD2, o, 4, 0b0100100000000000, 0x08c, SETMO , SETMOC, Ot::ModRM8, Ot::FixedRegister8(Register8::CL)); + inst!( 0xD2, o, 4, 0b0100100000000000, 0x08c, SAR , SAR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)); // Group - inst!( 0xD2, 4, 0b0100100000000000, 0x08c, ROL , ROL , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD2, 4, 0b0100100000000000, 0x08c, ROR , ROR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD2, 4, 0b0100100000000000, 0x08c, RCL , RCL , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD2, 4, 0b0100100000000000, 0x08c, RCR , RCR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD2, 4, 0b0100100000000000, 0x08c, SHL , SHL , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD2, 4, 0b0100100000000000, 0x08c, SHR , SHR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD2, 4, 0b0100100000000000, 0x08c, SETMO , SETMOC, Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD2, 4, 0b0100100000000000, 0x08c, SAR , SAR , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)), + inst!( 0xD3, o, 4, 0b0100100000000000, 0x08c, ROL , ROL , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)); + inst!( 0xD3, o, 4, 0b0100100000000000, 0x08c, ROR , ROR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)); + inst!( 0xD3, o, 4, 0b0100100000000000, 0x08c, RCL , RCL , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)); + inst!( 0xD3, o, 4, 0b0100100000000000, 0x08c, RCR , RCR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)); + inst!( 0xD3, o, 4, 0b0100100000000000, 0x08c, SHL , SHL , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)); + inst!( 0xD3, o, 4, 0b0100100000000000, 0x08c, SHR , SHR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)); + inst!( 0xD3, o, 4, 0b0100100000000000, 0x08c, SETMO , SETMOC, Ot::ModRM16, Ot::FixedRegister8(Register8::CL)); + inst!( 0xD3, o, 4, 0b0100100000000000, 0x08c, SAR , SAR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)); // Group - inst!( 0xD3, 4, 0b0100100000000000, 0x08c, ROL , ROL , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD3, 4, 0b0100100000000000, 0x08c, ROR , ROR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD3, 4, 0b0100100000000000, 0x08c, RCL , RCL , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD3, 4, 0b0100100000000000, 0x08c, RCR , RCR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD3, 4, 0b0100100000000000, 0x08c, SHL , SHL , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD3, 4, 0b0100100000000000, 0x08c, SHR , SHR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD3, 4, 0b0100100000000000, 0x08c, SETMO , SETMOC, Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), - inst!( 0xD3, 4, 0b0100100000000000, 0x08c, SAR , SAR , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)), + inst!( 0xF6, o, 5, 0b0100100000100100, 0x098, TEST , Ot::ModRM8, Ot::Immediate8); + inst!( 0xF6, o, 5, 0b0100100000100100, 0x098, TEST , Ot::ModRM8, Ot::Immediate8); + inst!( 0xF6, o, 5, 0b0100100000100100, 0x098, NOT , NOT , Ot::ModRM8, Ot::NoOperand); + inst!( 0xF6, o, 5, 0b0100100000100100, 0x098, NEG , NEG , Ot::ModRM8, Ot::NoOperand); + inst!( 0xF6, o, 5, 0b0100100000100100, 0x098, MUL , Ot::ModRM8, Ot::NoOperand); + inst!( 0xF6, o, 5, 0b0100100000100100, 0x098, IMUL , Ot::ModRM8, Ot::NoOperand); + inst!( 0xF6, o, 5, 0b0100100000100100, 0x098, DIV , Ot::ModRM8, Ot::NoOperand); + inst!( 0xF6, o, 5, 0b0100100000100100, 0x098, IDIV , Ot::ModRM8, Ot::NoOperand); // Group - inst!( 0xF6, 5, 0b0100100000100100, 0x098, TEST , Ot::ModRM8, Ot::Immediate8), - inst!( 0xF6, 5, 0b0100100000100100, 0x098, TEST , Ot::ModRM8, Ot::Immediate8), - inst!( 0xF6, 5, 0b0100100000100100, 0x098, NOT , NOT , Ot::ModRM8, Ot::NoOperand), - inst!( 0xF6, 5, 0b0100100000100100, 0x098, NEG , NEG , Ot::ModRM8, Ot::NoOperand), - inst!( 0xF6, 5, 0b0100100000100100, 0x098, MUL , Ot::ModRM8, Ot::NoOperand), - inst!( 0xF6, 5, 0b0100100000100100, 0x098, IMUL , Ot::ModRM8, Ot::NoOperand), - inst!( 0xF6, 5, 0b0100100000100100, 0x098, DIV , Ot::ModRM8, Ot::NoOperand), - inst!( 0xF6, 5, 0b0100100000100100, 0x098, IDIV , Ot::ModRM8, Ot::NoOperand), + inst!( 0xF7, o, 5, 0b0100100000100100, 0x160, TEST , Ot::ModRM16, Ot::Immediate16); + inst!( 0xF7, o, 5, 0b0100100000100100, 0x160, TEST , Ot::ModRM16, Ot::Immediate16); + inst!( 0xF7, o, 5, 0b0100100000100100, 0x160, NOT , NOT , Ot::ModRM16, Ot::NoOperand); + inst!( 0xF7, o, 5, 0b0100100000100100, 0x160, NEG , NEG , Ot::ModRM16, Ot::NoOperand); + inst!( 0xF7, o, 5, 0b0100100000100100, 0x160, MUL , Ot::ModRM16, Ot::NoOperand); + inst!( 0xF7, o, 5, 0b0100100000100100, 0x160, IMUL , Ot::ModRM16, Ot::NoOperand); + inst!( 0xF7, o, 5, 0b0100100000100100, 0x160, DIV , Ot::ModRM16, Ot::NoOperand); + inst!( 0xF7, o, 5, 0b0100100000100100, 0x160, IDIV , Ot::ModRM16, Ot::NoOperand); // Group - inst!( 0xF7, 5, 0b0100100000100100, 0x160, TEST , Ot::ModRM16, Ot::Immediate16), - inst!( 0xF7, 5, 0b0100100000100100, 0x160, TEST , Ot::ModRM16, Ot::Immediate16), - inst!( 0xF7, 5, 0b0100100000100100, 0x160, NOT , NOT , Ot::ModRM16, Ot::NoOperand), - inst!( 0xF7, 5, 0b0100100000100100, 0x160, NEG , NEG , Ot::ModRM16, Ot::NoOperand), - inst!( 0xF7, 5, 0b0100100000100100, 0x160, MUL , Ot::ModRM16, Ot::NoOperand), - inst!( 0xF7, 5, 0b0100100000100100, 0x160, IMUL , Ot::ModRM16, Ot::NoOperand), - inst!( 0xF7, 5, 0b0100100000100100, 0x160, DIV , Ot::ModRM16, Ot::NoOperand), - inst!( 0xF7, 5, 0b0100100000100100, 0x160, IDIV , Ot::ModRM16, Ot::NoOperand), + inst!( 0xFE, o, 6, 0b0000100000100100, 0x020, INC , INC , Ot::ModRM8, Ot::NoOperand); + inst!( 0xFE, o, 6, 0b0000100000100100, 0x020, DEC , DEC , Ot::ModRM8, Ot::NoOperand); + inst!( 0xFE, o, 6, 0b0000100000100100, 0x020, CALL , Ot::ModRM8, Ot::NoOperand); + inst!( 0xFE, o, 6, 0b0000100000100100, 0x020, CALLF , Ot::ModRM8, Ot::NoOperand); + inst!( 0xFE, o, 6, 0b0000100000100100, 0x020, JMP , Ot::ModRM8, Ot::NoOperand); + inst!( 0xFE, o, 6, 0b0000100000100100, 0x020, JMPF , Ot::ModRM8, Ot::NoOperand); + inst!( 0xFE, o, 6, 0b0000100000100100, 0x020, PUSH , Ot::ModRM8, Ot::NoOperand); + inst!( 0xFE, o, 6, 0b0000100000100100, 0x020, PUSH , Ot::ModRM8, Ot::NoOperand); // Group - inst!( 0xFE, 6, 0b0000100000100100, 0x020, INC , INC , Ot::ModRM8, Ot::NoOperand), - inst!( 0xFE, 6, 0b0000100000100100, 0x020, DEC , DEC , Ot::ModRM8, Ot::NoOperand), - inst!( 0xFE, 6, 0b0000100000100100, 0x020, CALL , Ot::ModRM8, Ot::NoOperand), - inst!( 0xFE, 6, 0b0000100000100100, 0x020, CALLF , Ot::ModRM8, Ot::NoOperand), - inst!( 0xFE, 6, 0b0000100000100100, 0x020, JMP , Ot::ModRM8, Ot::NoOperand), - inst!( 0xFE, 6, 0b0000100000100100, 0x020, JMPF , Ot::ModRM8, Ot::NoOperand), - inst!( 0xFE, 6, 0b0000100000100100, 0x020, PUSH , Ot::ModRM8, Ot::NoOperand), - inst!( 0xFE, 6, 0b0000100000100100, 0x020, PUSH , Ot::ModRM8, Ot::NoOperand), - // Group - inst!( 0xFF, 6, 0b0000100000100100, 0x026, INC , INC , Ot::ModRM16, Ot::NoOperand), - inst!( 0xFF, 6, 0b0000100000100100, 0x026, DEC , DEC , Ot::ModRM16, Ot::NoOperand), - inst!( 0xFF, 6, 0b0000100000100100, 0x026, CALL , Ot::ModRM16, Ot::NoOperand), - inst!( 0xFF, 6, 0b0000100000100100, 0x026, CALLF , Ot::ModRM16, Ot::NoOperand), - inst!( 0xFF, 6, 0b0000100000100100, 0x026, JMP , Ot::ModRM16, Ot::NoOperand), - inst!( 0xFF, 6, 0b0000100000100100, 0x026, JMPF , Ot::ModRM16, Ot::NoOperand), - inst!( 0xFF, 6, 0b0000100000100100, 0x026, PUSH , Ot::ModRM16, Ot::NoOperand), - inst!( 0xFF, 6, 0b0000100000100100, 0x026, PUSH , Ot::ModRM16, Ot::NoOperand), -]; + inst!( 0xFF, o, 6, 0b0000100000100100, 0x026, INC , INC , Ot::ModRM16, Ot::NoOperand); + inst!( 0xFF, o, 6, 0b0000100000100100, 0x026, DEC , DEC , Ot::ModRM16, Ot::NoOperand); + inst!( 0xFF, o, 6, 0b0000100000100100, 0x026, CALL , Ot::ModRM16, Ot::NoOperand); + inst!( 0xFF, o, 6, 0b0000100000100100, 0x026, CALLF , Ot::ModRM16, Ot::NoOperand); + inst!( 0xFF, o, 6, 0b0000100000100100, 0x026, JMP , Ot::ModRM16, Ot::NoOperand); + inst!( 0xFF, o, 6, 0b0000100000100100, 0x026, JMPF , Ot::ModRM16, Ot::NoOperand); + inst!( 0xFF, o, 6, 0b0000100000100100, 0x026, PUSH , Ot::ModRM16, Ot::NoOperand); + inst!( 0xFF, o, 6, 0b0000100000100100, 0x026, PUSH , Ot::ModRM16, Ot::NoOperand); + // END OF REGULAR INTEL OPCODES (0-367) + // FF extended opcodes follow. Thankfully, on V20 none of these are group opcodes. + inst_skip!(o, 0x10); // Skip 0F00->0F0F + inst!( 0x10, o, 0, 0b0000100000100100, 0x000, TEST1 , Ot::ModRM8, Ot::FixedRegister8(Register8::CL)); + inst!( 0x11, o, 0, 0b0000100000100100, 0x000, TEST1 , Ot::ModRM16, Ot::FixedRegister8(Register8::CL)); + o.table +}; impl NecVx0 { #[rustfmt::skip] @@ -541,12 +588,24 @@ impl NecVx0 { let mut size: u32 = 1; let mut op_prefixes: u32 = 0; let mut op_segment_override = None; - let mut decode_idx: usize; + let mut decode_idx: usize = 0; + let mut op_prefix_ct = 0; // Read in opcode prefixes until exhausted loop { // Set flags for all prefixes encountered... op_prefixes |= match opcode { + 0x0F => { + op_prefixes |= OPCODE_PREFIX_0F; + // 0F prefixed-instructions exist in table after all regular Intel instructions + // Nothing can follow an 0F prefix; so start instruction now. Fetching the + // extended opcode counts as a Subsequent write based on queue status flags. + opcode = bytes.q_read_u8(QueueType::Subsequent, QueueReader::Biu); + decode_idx = REGULAR_OPS_LEN; + bytes.wait(1); + size += 1; + break; + } 0x26 => OPCODE_PREFIX_ES_OVERRIDE, 0x2E => OPCODE_PREFIX_CS_OVERRIDE, 0x36 => OPCODE_PREFIX_SS_OVERRIDE, @@ -555,10 +614,14 @@ impl NecVx0 { 0xF1 => OPCODE_PREFIX_LOCK, 0xF2 => OPCODE_PREFIX_REP1, 0xF3 => OPCODE_PREFIX_REP2, + 0x64 => OPCODE_PREFIX_REP3, + 0x65 => OPCODE_PREFIX_REP4, _=> { break; } }; + op_prefix_ct += 1; + // ... but only store the last segment override prefix seen op_segment_override = match opcode { 0x26 => Some(Segment::ES), @@ -576,7 +639,10 @@ impl NecVx0 { size += 1; } - decode_idx = opcode as usize; + // Pack number of prefixes decoded into prefix field (maximum of 3) + op_prefixes |= std::cmp::max(op_prefix_ct, 3); + + decode_idx += opcode as usize; let mut op_lu = &DECODE[decode_idx]; let mut modrm= ModRmByte::default(); let mut loaded_modrm = false; @@ -789,17 +855,23 @@ impl NecVx0 { if !matches!(op_lu.operand2, OperandTemplate::NoTemplate) { (operand2_type, operand2_size) = match_op(op_lu.operand2); } - - // Hack for 3-operand instructions + + // Hacks for irregular-operand instructions match opcode { 0x69 => { - // imm16 + // 3rd operand, imm16 size += 2; } 0x6B => { - // imm8 + // 3rd operand, imm8 size += 1; } + 0xC8 => { + // imm16, imm8 + // TODO: Handle this more gracefully + let (imm8, _imm16) = bytes.q_peek_farptr16(); + operand2_type = OperandType::Immediate8((imm8 & 0xFF) as u8); + } _ => {} } diff --git a/core/src/cpu_vx0/execute.rs b/core/src/cpu_vx0/execute.rs index d8f6709c..5f56046d 100644 --- a/core/src/cpu_vx0/execute.rs +++ b/core/src/cpu_vx0/execute.rs @@ -31,7 +31,18 @@ */ use crate::{ - cpu_common::{CpuException, ExecutionResult, Mnemonic, OperandType, QueueOp, Segment}, + cpu_common::{ + CpuException, + ExecutionResult, + Mnemonic, + OperandType, + QueueOp, + Segment, + OPCODE_PREFIX_REP1, + OPCODE_PREFIX_REP2, + OPCODE_PREFIX_REP3, + OPCODE_PREFIX_REPMASK, + }, cpu_vx0::{biu::*, *}, util, }; @@ -127,12 +138,15 @@ impl NecVx0 { } // Check for REPx prefixes - if (self.i.prefixes & OPCODE_PREFIX_REP1 != 0) || (self.i.prefixes & OPCODE_PREFIX_REP2 != 0) { + if (self.i.prefixes & OPCODE_PREFIX_REPMASK != 0) { // A REPx prefix was set let mut invalid_rep = false; match self.i.mnemonic { + Mnemonic::INSB | Mnemonic::INSW | Mnemonic::OUTSB | Mnemonic::OUTSW => { + self.rep_type = RepType::Rep; + } Mnemonic::STOSB | Mnemonic::STOSW | Mnemonic::LODSB | Mnemonic::LODSW | Mnemonic::MOVSB | Mnemonic::MOVSW => { self.rep_type = RepType::Rep; } @@ -141,9 +155,18 @@ impl NecVx0 { if self.i.prefixes & OPCODE_PREFIX_REP1 != 0 { self.rep_type = RepType::Repne; } - else { + else if self.i.prefixes & OPCODE_PREFIX_REP2 != 0 { self.rep_type = RepType::Repe; } + else if self.i.prefixes & OPCODE_PREFIX_REP3 != 0 { + self.rep_type = RepType::Repnc; + } + else if self.i.prefixes & OPCODE_PREFIX_REP3 != 0 { + self.rep_type = RepType::Repc; + } + else { + invalid_rep = true; + } } Mnemonic::MUL | Mnemonic::IMUL | Mnemonic::DIV | Mnemonic::IDIV => { // REP prefix on MUL/DIV negates the product/quotient. @@ -411,7 +434,7 @@ impl NecVx0 { self.pop_register16(Register16::CX, ReadWriteFlag::Normal); self.pop_register16(Register16::AX, ReadWriteFlag::RNI); } - 0x62 => { + 0x62 | 0x63 => { // BOUND let idx = self.read_operand16(self.i.operand1_type, None).unwrap() as i16; @@ -441,6 +464,7 @@ impl NecVx0 { self.halted = true; } } + 0x64 | 0x65 => {}, 0x68 => { // PUSH imm16 let imm16 = self.read_operand16(self.i.operand1_type, None).unwrap(); @@ -482,7 +506,39 @@ impl NecVx0 { self.set_szp_flags_from_result_u8(product as u8); } - 0x63..=0x6F => { + 0x6C | 0x6D | 0x6E | 0x6F => { + // INSB | INSW | OUTSB | OUTSW + // rep_start() will terminate early if CX==0 + if self.rep_start() { + self.string_op(self.i.mnemonic, self.i.segment_override); + self.cycle_i(0x130); + + // Check for end condition (CX==0) + if self.in_rep { + self.decrement_register16(Register16::CX); // 131 + // Check for interrupt + if self.intr_pending { + self.cycles_i(2, &[0x131, MC_JUMP]); // Jump to RPTI + self.rep_interrupt(); + } + else { + self.cycles_i(2, &[0x131, 0x132]); + if self.c.x() == 0 { + // Fall through to 133, RNI + self.rep_end(); + } + else { + self.cycle_i(MC_JUMP); // jump to 1 + } + } + } + else { + // End non-rep prefixed MOVSB + self.cycle_i(MC_JUMP); // jump to 133, RNI + } + } + } + 0x6C..=0x6F => { unhandled = true; } 0x70..=0x7F => { @@ -1143,7 +1199,36 @@ impl NecVx0 { self.cycle_i(0x01e); self.write_operand16(self.i.operand1_type, self.i.segment_override, op2_value, ReadWriteFlag::RNI); } - 0xC8 | 0xCA => { + 0xC8 => { + // ENTER imm16, imm8 + let alloc_size = self.read_operand16(self.i.operand1_type, None).unwrap(); + let nesting_level = self.read_operand8(self.i.operand2_type, None).unwrap(); + + // First, the old BP value is saved to the stack so that the BP of the calling procedure + // can be restored + self.push_register16(Register16::BP, ReadWriteFlag::Normal); + let frame_temp = self.get_register16(Register16::SP); + + if nesting_level > 1 { + for i in 0..nesting_level { + self.set_register16(Register16::BP, self.get_register16(Register16::BP).wrapping_sub(2)); + let ptr = self.biu_read_u16(Segment::SS, self.get_register16(Register16::BP), ReadWriteFlag::Normal); + self.push_u16(ptr, ReadWriteFlag::Normal); + } + } + else if nesting_level == 1 { + self.push_u16(frame_temp, ReadWriteFlag::Normal); + }; + self.set_register16(Register16::BP, frame_temp); + self.set_register16(Register16::SP, self.get_register16(Register16::SP).wrapping_sub(alloc_size)); + } + 0xC9 => { + // LEAVE + self.set_register16(Register16::SP, self.get_register16(Register16::BP)); + let new_bp = self.pop_u16(); + self.set_register16(Register16::BP, new_bp); + } + 0xCA => { // RETF imm16 - Far Return w/ release // 0xC8 undocumented alias for 0xCA let stack_disp = self.read_operand16(self.i.operand1_type, None).unwrap(); @@ -1152,7 +1237,7 @@ impl NecVx0 { self.cycle_i(0x0ce); jump = true; } - 0xC9 | 0xCB => { + 0xCB => { // RETF - Far Return // 0xC9 undocumented alias for 0xCB self.cycle_i(0x0c0); @@ -1304,7 +1389,7 @@ impl NecVx0 { self.set_register8(Register8::AL, value); } - 0xD8..=0xDF => { + 0x66 | 0x67 | 0xD8..=0xDF => { // ESC - FPU instructions. // Perform dummy read if memory operand @@ -1382,7 +1467,7 @@ impl NecVx0 { let op2_value = self.read_operand8(self.i.operand2_type, self.i.segment_override).unwrap(); self.cycles_i(2, &[0x0ad, 0x0ae]); - let in_word = self.biu_io_read_u16(op2_value as u16, ReadWriteFlag::Normal); + let in_word = self.biu_io_read_u16(op2_value as u16); self.set_register16(Register16::AX, in_word); } 0xE6 => { @@ -1484,7 +1569,7 @@ impl NecVx0 { 0xED => { // IN ax, dx let op2_value = self.read_operand16(self.i.operand2_type, self.i.segment_override).unwrap(); - let in_word = self.biu_io_read_u16(op2_value, ReadWriteFlag::Normal); + let in_word = self.biu_io_read_u16(op2_value); self.set_register16(Register16::AX, in_word); } 0xEE => { @@ -2214,4 +2299,108 @@ impl NecVx0 { } } } + + /// Execute an extended opcode (Prefixed with 0F). + /// We can make some optimizations here as no instructions here take a REP prefix or perform + /// flow control. + #[rustfmt::skip] + pub fn execute_extended_instruction(&mut self) -> ExecutionResult { + let mut unhandled: bool = false; + let mut jump: bool = false; + let mut exception: CpuException = CpuException::NoException; + + self.step_over_target = None; + + self.trace_comment("EXECUTE_EXT"); + + // TODO: Check optimization here. We could reset several flags at once if they were in a + // bitfield. + // Reset instruction reentrancy flag + self.instruction_reentrant = false; + + // Reset jumped flag. + self.jumped = false; + + // Reset trap suppression flag + self.trap_suppressed = false; + + // Decrement trap counters. + self.trap_enable_delay = self.trap_enable_delay.saturating_sub(1); + self.trap_disable_delay = self.trap_disable_delay.saturating_sub(1); + + // If we have an NX loaded RNI cycle from the previous instruction, execute it. + // Otherwise, wait one cycle before beginning instruction if there was no modrm. + if self.nx { + self.trace_comment("RNI"); + self.cycle(); + self.nx = false; + } else if self.last_queue_op == QueueOp::First { + self.cycle(); + } + + match self.i.opcode { + 0x10 => { + // TEST1, r/m8, CL + let op1_value = self.read_operand8(self.i.operand1_type, self.i.segment_override).unwrap(); + let bit_n = self.get_register8(Register8::CL) & 0x03; // Mask CL to 3 bits. + self.cycles(2 ); + self.set_flag_state(Flag::Zero, (1< { + // TEST1, r/m16, CL + let op1_value = self.read_operand16(self.i.operand1_type, self.i.segment_override).unwrap(); + let bit_n = self.get_register8(Register8::CL) & 0x0F; // Mask CL to 4 bits. + self.cycles(2 ); + self.set_flag_state(Flag::Zero, (1< { + unhandled = true; + } + } + + // Reset REP init flag. This flag is set after a rep-prefixed instruction is executed for the first time. It + // should be preserved between executions of a rep-prefixed instruction unless an interrupt occurs, in which + // case the rep-prefix instruction terminates normally after RPTI. This flag determines whether RPTS is + // run when executing the instruction. + if !self.in_rep { + self.rep_init = false; + } + else { + self.instruction_reentrant = true; + } + + if unhandled { + unreachable!("Invalid opcode!"); + //ExecutionResult::UnsupportedOpcode(self.i.opcode) + } + else if self.halted && !self.reported_halt && !self.get_flag(Flag::Interrupt) && !self.get_flag(Flag::Trap) { + // CPU was halted with interrupts disabled - will not continue + self.reported_halt = true; + ExecutionResult::Halt + } + else if jump { + ExecutionResult::OkayJump + } + else if self.in_rep { + if let RepType::MulDiv = self.rep_type { + // Rep prefix on MUL/DIV just sets flags, do not rep + self.in_rep = false; + Okay + } + else { + self.rep_init = true; + // Set step-over target so that we can skip long REP instructions. + // Normally the step behavior during REP is to perform a single iteration. + self.step_over_target = Some(CpuAddress::Segmented(self.cs, self.ip())); + ExecutionResult::OkayRep + } + } + else { + match exception { + CpuException::DivideError => ExecutionResult::ExceptionError(exception), + CpuException::BoundsException => ExecutionResult::ExceptionError(exception), + CpuException::NoException => Okay, + } + } + } } diff --git a/core/src/cpu_vx0/fuzzer.rs b/core/src/cpu_vx0/fuzzer.rs index 0ff03def..d1d88197 100644 --- a/core/src/cpu_vx0/fuzzer.rs +++ b/core/src/cpu_vx0/fuzzer.rs @@ -51,6 +51,15 @@ macro_rules! get_rand_range { }; } +const DISABLE_SEG_OVERRIDES: [u8; 113] = [ + 0x06, 0x07, 0x0E, 0x16, 0x17, 0x1E, 0x1F, 0x27, 0x2F, 0x37, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, + 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x68, 0x6A, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x98, 0x99, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA8, 0xA9, 0xB0, 0xB1, 0xB2, 0xB3, + 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, + 0xCF, 0xD4, 0xD5, 0xE4, 0xE5, 0xE6, 0xE7, 0xEC, 0xED, 0xEE, 0xEF, 0xF5, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, +]; + impl NecVx0 { #[allow(dead_code)] pub fn randomize_seed(&mut self, mut seed: u64) { @@ -105,20 +114,33 @@ impl NecVx0 { } #[allow(dead_code)] - pub fn random_inst_from_opcodes(&mut self, opcode_list: &[u8]) { + pub fn random_inst_from_opcodes(&mut self, opcode_list: &[u8], prefix: Option) { let mut instr: VecDeque = VecDeque::new(); // Randomly pick one opcode from the provided list let opcode_i = get_rand_range!(self, 0, opcode_list.len()); let opcode = opcode_list[opcode_i]; + if let Some(prefix) = prefix { + instr.push_back(prefix); + } instr.push_back(opcode); let mut enable_segment_prefix = true; + if DISABLE_SEG_OVERRIDES.contains(&opcode) { + enable_segment_prefix = false; + } + + let mut mask_nesting_level = false; + // Add rep prefixes to string ops with 50% probability let do_rep_prefix: u8 = get_rand!(self); match opcode { + 0xC8 => { + // ENTER + mask_nesting_level = true; + } 0xA4..=0xA7 | 0xAA..=0xAF => { // String ops match do_rep_prefix { @@ -137,8 +159,8 @@ impl NecVx0 { _ => {} } - // Mask CX to 8 bits. - //self.cx = self.cx & 0x00FF; + // Mask CX to 7 bits. + self.set_register16(Register16::CX, self.get_register16(Register16::CX) & 0x7F); } 0x9D => { // POPF. @@ -183,14 +205,6 @@ impl NecVx0 { self.c.set_l(self.c.l() & 0x3F); } - 0xC0..=0xC3 | 0xC8..=0xCF => { - // RETN, RETF, INT[X], IRET - enable_segment_prefix = false; - } - 0xF5 | 0xF8..=0xFD => { - // Clear/set flags - enable_segment_prefix = false; - } _ => {} } @@ -262,10 +276,17 @@ impl NecVx0 { } // Add five random instruction bytes (+modrm makes 6) - for _ in 0..5 { + for i in 0..5 { let instr_byte: u8 = get_rand!(self); - instr.push_back(instr_byte); + // This is actually the third byte, since we pushed one byte for modrm already + // (even if no modrm) + if mask_nesting_level && (i == 1) { + instr.push_back(instr_byte & 0x1F); + } + else { + instr.push_back(instr_byte); + } } // Copy instruction to memory at CS:IP @@ -336,25 +357,13 @@ impl NecVx0 { // Filter out invalid forms of some instructions that cannot // reasonably be validated. - match opcode { - // FF group opcode - 0xFF => { - match modrm_byte & 0b00_111_000 { - 0b00_011_000 => { - // FF.3 CALLF - if modrm_byte & 0xC0 == 0xC0 { - // Reg form, invalid. - continue; - } - } - 0b00_101_000 => { - // FF.5 JMPF - if modrm_byte & 0xC0 == 0xC0 { - // Reg form, invalid. - continue; - } - } - _ => {} + match (opcode, extension) { + // FE & FF group opcode + (0xFE | 0xFF, 0x03 | 0x05) => { + // FE.3 FE.5 FF.3 FF.5 CALLF + if modrm_byte & 0xC0 == 0xC0 { + // Reg form, invalid. + continue; } } _ => {} diff --git a/core/src/cpu_vx0/gdr.rs b/core/src/cpu_vx0/gdr.rs index 88a483c5..e1c3a480 100644 --- a/core/src/cpu_vx0/gdr.rs +++ b/core/src/cpu_vx0/gdr.rs @@ -52,6 +52,7 @@ pub const GDR_FORCE_BYTE: u16 = 0b0001_0000_0000_0000; // Instruction forces a b pub const GDR_L8: u16 = 0b0010_0000_0000_0000; // Instruction sets L8 flag pub const GDR_UPDATE_CARRY: u16 = 0b0100_0000_0000_0000; // Instruction updates Carry flag +#[derive(Copy, Clone, Default)] pub struct GdrEntry(pub u16); impl GdrEntry { diff --git a/core/src/cpu_vx0/mod.rs b/core/src/cpu_vx0/mod.rs index 5927d451..3b2dd883 100644 --- a/core/src/cpu_vx0/mod.rs +++ b/core/src/cpu_vx0/mod.rs @@ -173,25 +173,6 @@ pub const MAX_INSTRUCTION_SIZE: usize = 15; const OPCODE_REGISTER_SELECT_MASK: u8 = 0b0000_0111; -// Instruction flags -const I_USES_MEM: u32 = 0b0000_0001; // Instruction has a memory operand -const I_HAS_MODRM: u32 = 0b0000_0010; // Instruction has a modrm byte -const I_LOCKABLE: u32 = 0b0000_0100; // Instruction compatible with LOCK prefix -const I_REL_JUMP: u32 = 0b0000_1000; -const I_LOAD_EA: u32 = 0b0001_0000; // Instruction loads from its effective address -const I_GROUP_DELAY: u32 = 0b0010_0000; // Instruction has cycle delay for being a specific group instruction - -// Instruction prefixes -pub const OPCODE_PREFIX_ES_OVERRIDE: u32 = 0b_0000_0000_0001; -pub const OPCODE_PREFIX_CS_OVERRIDE: u32 = 0b_0000_0000_0010; -pub const OPCODE_PREFIX_SS_OVERRIDE: u32 = 0b_0000_0000_0100; -pub const OPCODE_PREFIX_DS_OVERRIDE: u32 = 0b_0000_0000_1000; -pub const OPCODE_SEG_OVERRIDE_MASK: u32 = 0b_0000_0000_1111; -pub const OPCODE_PREFIX_WAIT: u32 = 0b_0000_0100_0000; -pub const OPCODE_PREFIX_LOCK: u32 = 0b_0000_1000_0000; -pub const OPCODE_PREFIX_REP1: u32 = 0b_0001_0000_0000; -pub const OPCODE_PREFIX_REP2: u32 = 0b_0010_0000_0000; - // The parity flag is calculated from the lower 8 bits of an alu operation regardless // of the operand width. It is trivial to precalculate an 8-bit parity table. pub const PARITY_TABLE: [bool; 256] = { @@ -383,6 +364,8 @@ pub enum RepType { Rep, Repne, Repe, + Repnc, + Repc, MulDiv, } diff --git a/core/src/cpu_vx0/step.rs b/core/src/cpu_vx0/step.rs index e6268d13..d6453cd5 100644 --- a/core/src/cpu_vx0/step.rs +++ b/core/src/cpu_vx0/step.rs @@ -31,7 +31,7 @@ */ use crate::{ - cpu_common::{CpuError, CpuException, ExecutionResult, StepResult}, + cpu_common::{CpuError, CpuException, ExecutionResult, StepResult, OPCODE_PREFIX_0F}, cpu_vx0::{decode::DECODE, *}, vgdr, }; @@ -199,7 +199,12 @@ impl NecVx0 { } // Execute the current decoded instruction. - self.exec_result = self.execute_instruction(); + if self.i.prefixes & OPCODE_PREFIX_0F == 0 { + self.exec_result = self.execute_instruction(); + } + else { + self.exec_result = self.execute_extended_instruction(); + } let step_result = match &self.exec_result { ExecutionResult::Okay => { diff --git a/core/src/cpu_vx0/string.rs b/core/src/cpu_vx0/string.rs index 6205066a..13452f8b 100644 --- a/core/src/cpu_vx0/string.rs +++ b/core/src/cpu_vx0/string.rs @@ -40,6 +40,66 @@ impl NecVx0 { let segment_base_ds = segment_override.unwrap_or(Segment::DS); match opcode { + Mnemonic::INSB => { + let io_value = self.biu_io_read_u8(self.get_register16(Register16::DX)); + self.biu_write_u8(Segment::ES, self.di, io_value, ReadWriteFlag::Normal); + + match self.get_flag(Flag::Direction) { + false => { + // Direction flag clear, process forwards + self.di = self.di.wrapping_add(1); + } + true => { + // Direction flag set, process backwards + self.di = self.di.wrapping_sub(1); + } + } + } + Mnemonic::INSW => { + let io_value = self.biu_io_read_u16(self.get_register16(Register16::DX)); + self.biu_write_u16(Segment::ES, self.di, io_value, ReadWriteFlag::Normal); + + match self.get_flag(Flag::Direction) { + false => { + // Direction flag clear, process forwards + self.di = self.di.wrapping_add(2); + } + true => { + // Direction flag set, process backwards + self.di = self.di.wrapping_sub(2); + } + } + } + Mnemonic::OUTSW => { + let mem_value = self.biu_read_u16(Segment::ES, self.di, ReadWriteFlag::Normal); + self.biu_io_write_u16(self.get_register16(Register16::DX), mem_value, ReadWriteFlag::Normal); + + match self.get_flag(Flag::Direction) { + false => { + // Direction flag clear, process forwards + self.di = self.di.wrapping_add(2); + } + true => { + // Direction flag set, process backwards + self.di = self.di.wrapping_sub(2); + } + } + } + Mnemonic::OUTSB => { + let mem_value = self.biu_read_u8(Segment::ES, self.di); + self.biu_io_write_u8(self.get_register16(Register16::DX), mem_value, ReadWriteFlag::Normal); + + match self.get_flag(Flag::Direction) { + false => { + // Direction flag clear, process forwards + self.di = self.di.wrapping_add(1); + } + true => { + // Direction flag set, process backwards + self.di = self.di.wrapping_sub(1); + } + } + } Mnemonic::STOSB => { // STOSB - Write AL to [es:di] (ES prefix cannot be overridden) // No flags affected diff --git a/frontends/martypc_desktop_wgpu/src/cpu_test/common.rs b/frontends/martypc_desktop_wgpu/src/cpu_test/common.rs index 904be2ae..e7777e21 100644 --- a/frontends/martypc_desktop_wgpu/src/cpu_test/common.rs +++ b/frontends/martypc_desktop_wgpu/src/cpu_test/common.rs @@ -123,7 +123,7 @@ pub type Metadata = HashMap; macro_rules! trace_error { ($wr:expr, $($t:tt)*) => {{ let formatted_message = format!($($t)*); - log::error!("{}", &formatted_message); + //log::error!("{}", &formatted_message); // Assuming you want to write the message to the BufWriter as well. writeln!($wr, "{}", &formatted_message).expect("Failed to write to BufWriter"); diff --git a/frontends/martypc_desktop_wgpu/src/cpu_test/gen_tests.rs b/frontends/martypc_desktop_wgpu/src/cpu_test/gen_tests.rs index afde0303..2e77fca8 100644 --- a/frontends/martypc_desktop_wgpu/src/cpu_test/gen_tests.rs +++ b/frontends/martypc_desktop_wgpu/src/cpu_test/gen_tests.rs @@ -313,7 +313,7 @@ pub fn run_gentests(config: &ConfigFileParams) { cpu.random_grp_instruction(test_opcode, &[op_ext]); } else { - cpu.random_inst_from_opcodes(&[test_opcode]); + cpu.random_inst_from_opcodes(&[test_opcode], config.tests.test_opcode_prefix); } if cpu.get_ip() != cpu.get_register16(Register16::PC) { @@ -363,6 +363,13 @@ pub fn run_gentests(config: &ConfigFileParams) { .peek_range(instruction_address as usize, i.size as usize) .unwrap(); + if test_num < config.tests.test_start.unwrap_or(0) { + println!( + "Test {}: Skipping test for instruction: {} opcode:{:02X} addr:{:05X} bytes: {:X?}", + test_num, i, opcode, i.address, bytes + ); + continue; + } println!( "Test {}: Creating test for instruction: {} opcode:{:02X} addr:{:05X} bytes: {:X?}", test_num, i, opcode, i.address, bytes @@ -389,7 +396,7 @@ pub fn run_gentests(config: &ConfigFileParams) { | Mnemonic::LODSW | Mnemonic::SCASB | Mnemonic::SCASW => { - // limit cx to 31 + // limit cx to 127 cpu.set_register16(Register16::CX, cpu.get_register16(Register16::CX) & 0x7F); rep = true; } diff --git a/frontends/martypc_desktop_wgpu/src/cpu_test/run_tests.rs b/frontends/martypc_desktop_wgpu/src/cpu_test/run_tests.rs index 31187be0..a0e7a47a 100644 --- a/frontends/martypc_desktop_wgpu/src/cpu_test/run_tests.rs +++ b/frontends/martypc_desktop_wgpu/src/cpu_test/run_tests.rs @@ -78,6 +78,8 @@ use marty_core::{ tracelogger::TraceLogger, }; +static METADATA_FILE: &str = "metadata.json"; + pub fn run_runtests(config: ConfigFileParams) { let mut test_path = "./tests".to_string(); if let Some(test_dir) = &config.tests.test_dir { @@ -95,10 +97,10 @@ pub fn run_runtests(config: ConfigFileParams) { // Load metadata file. let mut metadata_path = test_base_path.clone(); - metadata_path.push("8088.json"); + metadata_path.push(String::from(METADATA_FILE)); let mut metadata_file = File::open(metadata_path.clone()).expect(&format!( - "Couldn't open metadata file 8088.json at path: {:?}", - metadata_path + "Couldn't open metadata file '{}' at path: {:?}", + METADATA_FILE, metadata_path )); let mut contents = String::new(); metadata_file @@ -324,12 +326,14 @@ fn run_tests( #[cfg(feature = "cpu_validator")] use marty_core::cpu_validator::ValidatorMode; + let cpu_type = config.tests.test_cpu_type.unwrap_or(CpuType::Intel8088); + let mut cpu; #[cfg(feature = "cpu_validator")] { cpu = match CpuBuilder::new() - .with_cpu_type(CpuType::Intel8088) - .with_cpu_subtype(CpuSubType::Intel8088) + .with_cpu_type(cpu_type) + //.with_cpu_subtype(CpuSubType::Intel8088) .with_trace_mode(trace_mode) .with_trace_logger(trace_logger) .with_validator_type(ValidatorType::None) @@ -543,6 +547,8 @@ fn run_tests( // Validate final register state. let vregs = cpu.get_vregisters(); + let mut current_test_failed = false; + if validate_registers( &metadata, opcode, @@ -612,99 +618,78 @@ fn run_tests( reason: FailType::RegMismatch, }; - results.failed += 1; + current_test_failed = true; results.reg_mismatch += 1; - results.failed_tests.push_back(item); if stop_on_failure { break; } - continue; } - // Validate cycles - if test.cycles.len() == cpu_cycles.len() { - _ = writeln!( - log, - "{}| Test {:05}:{} Test cycles {} match CPU cycles: {}", - opcode_string, - n, - &test.name, - test.cycles.len(), - cpu_cycles.len() - ); - } - else if ((test.cycles.len() as i32 - cpu_cycles.len() as i32).abs() > 1) { - // If the difference is more than 1, the test has failed. - trace_error!( - log, - "{}| Test {:05}:{} Test cycles {} DO NOT MATCH CPU cycles: {}", - opcode_string, - n, - &test.name, - test.cycles.len(), - cpu_cycles.len() - ); - - print_cycle_diff(log, &test.cycles, &cpu_cycles); - cpu.trace_flush(); + let do_validate_cycles = config.tests.test_run_validate_cycles.unwrap_or(true); - trace_error!( - log, - "{}| Test {:05}: Test hash {} failed.", - opcode_string, - n, - &test.test_hash - ); - - let item = TestFailItem { - num: n as u32, - name: test.name.clone(), - reason: FailType::CycleMismatch, - }; + if do_validate_cycles { + // Validate cycles + if test.cycles.len() == cpu_cycles.len() { + _ = writeln!( + log, + "{}| Test {:05}:{} Test cycles {} match CPU cycles: {}", + opcode_string, + n, + &test.name, + test.cycles.len(), + cpu_cycles.len() + ); + } + else if ((test.cycles.len() as i32 - cpu_cycles.len() as i32).abs() > 1) { + // If the difference is more than 1, the test has failed. + trace_error!( + log, + "{}| Test {:05}:{} Test cycles {} DO NOT MATCH CPU cycles: {}", + opcode_string, + n, + &test.name, + test.cycles.len(), + cpu_cycles.len() + ); - results.failed += 1; - results.cycle_mismatch += 1; - results.failed_tests.push_back(item); + print_cycle_diff(log, &test.cycles, &cpu_cycles); + cpu.trace_flush(); - if stop_on_failure { - break; - } - continue; - } - else if ((test.cycles.len() as i32 - cpu_cycles.len() as i32).abs() == 1) { - // A cycle difference of only 1 is acceptable (for now) - _ = writeln!( - log, - "{}| Test {:05}:{} Test cycles {} have ONE CYCLE variance to CPU cycles: {}", - opcode_string, - n, - &test.name, - test.cycles.len(), - cpu_cycles.len() - ); + trace_error!( + log, + "{}| Test {:05}: Test hash {} failed.", + opcode_string, + n, + &test.test_hash + ); - print_cycle_diff(log, &test.cycles, &cpu_cycles); - cpu.trace_flush(); + let item = TestFailItem { + num: n as u32, + name: test.name.clone(), + reason: FailType::CycleMismatch, + }; - let item = TestFailItem { - num: n as u32, - name: test.name.clone(), - reason: FailType::CycleMismatch, - }; + current_test_failed = true; + results.cycle_mismatch += 1; + results.failed_tests.push_back(item); - results.warning += 1; - results.cycle_mismatch += 1; - results.warn_tests.push_back(item); - continue; - } - else { - // Cycle counts match, so we can do a full cycle validation. - let (validate_result, _) = validate_cycles(&test.cycles, &cpu_cycles, log); - if validate_result { - _ = writeln!(log, "{}| Test {:05}: Test cycles validated!", opcode_string, n); + if stop_on_failure { + break; + } } - else { + else if ((test.cycles.len() as i32 - cpu_cycles.len() as i32).abs() == 1) { + // A cycle difference of only 1 is acceptable (for now) + _ = writeln!( + log, + "{}| Test {:05}:{} Test cycles {} have ONE CYCLE variance to CPU cycles: {}", + opcode_string, + n, + &test.name, + test.cycles.len(), + cpu_cycles.len() + ); + print_cycle_diff(log, &test.cycles, &cpu_cycles); cpu.trace_flush(); @@ -714,14 +699,34 @@ fn run_tests( reason: FailType::CycleMismatch, }; - results.failed += 1; + results.warning += 1; results.cycle_mismatch += 1; - results.failed_tests.push_back(item); + results.warn_tests.push_back(item); + } + else { + // Cycle counts match, so we can do a full cycle validation. + let (validate_result, _) = validate_cycles(&test.cycles, &cpu_cycles, log); + if validate_result { + _ = writeln!(log, "{}| Test {:05}: Test cycles validated!", opcode_string, n); + } + else { + print_cycle_diff(log, &test.cycles, &cpu_cycles); + cpu.trace_flush(); - if stop_on_failure { - break; + let item = TestFailItem { + num: n as u32, + name: test.name.clone(), + reason: FailType::CycleMismatch, + }; + + current_test_failed = true; + results.cycle_mismatch += 1; + results.failed_tests.push_back(item); + + if stop_on_failure { + break; + } } - continue; } } @@ -745,19 +750,25 @@ fn run_tests( reason: FailType::CycleMismatch, }; - results.failed += 1; + current_test_failed = true; results.mem_mismatch += 1; results.failed_tests.push_back(item); + print_cycle_diff(log, &test.cycles, &cpu_cycles); + cpu.trace_flush(); + if stop_on_failure { break; } - continue; } }; - // If we got here, we passed! - results.passed += 1; + if current_test_failed { + results.failed += 1; + } + else { + results.passed += 1; + } } results.duration = test_start.elapsed(); diff --git a/lib/frontend/config_toml_bpaf/Cargo.toml b/lib/frontend/config_toml_bpaf/Cargo.toml index 0b647f6f..737135e4 100644 --- a/lib/frontend/config_toml_bpaf/Cargo.toml +++ b/lib/frontend/config_toml_bpaf/Cargo.toml @@ -12,10 +12,10 @@ crate-type = ["lib"] [dependencies] marty_common = { path = "../../common" } -log = "0.4" -anyhow = "1.0.58" marty_core = { path = "../../../core" } frontend_common = { path = "../frontend_common" } +log = "0.4" +anyhow = "1.0.58" bpaf = { version = "0.7.7", features = ["derive"] } toml.workspace = true serde = { workspace = true, features = ["derive"] } diff --git a/lib/frontend/config_toml_bpaf/src/coreconfig.rs b/lib/frontend/config_toml_bpaf/src/coreconfig.rs index 62e58556..06bfd0f9 100644 --- a/lib/frontend/config_toml_bpaf/src/coreconfig.rs +++ b/lib/frontend/config_toml_bpaf/src/coreconfig.rs @@ -24,7 +24,7 @@ -------------------------------------------------------------------------- - bpaf_toml_cnofig::coreconfig.rs + config_toml_bpaf::coreconfig.rs Routines to parse configuration file and command line arguments. diff --git a/lib/frontend/config_toml_bpaf/src/lib.rs b/lib/frontend/config_toml_bpaf/src/lib.rs index df611e0e..5a1a946f 100644 --- a/lib/frontend/config_toml_bpaf/src/lib.rs +++ b/lib/frontend/config_toml_bpaf/src/lib.rs @@ -24,7 +24,7 @@ -------------------------------------------------------------------------- - bpaf_toml_config::lib.rs + config_toml_bpaf::lib.rs Routines to parse configuration file and command line arguments. @@ -203,8 +203,10 @@ pub struct Tests { pub test_cpu_subtype: Option, pub test_mode: Option, pub test_seed: Option, + pub test_start: Option, pub test_dir: Option, pub test_output_dir: Option, + pub test_opcode_prefix: Option, pub test_opcode_range: Option>, pub test_extension_range: Option>, pub test_opcode_exclude_list: Option>, @@ -214,6 +216,10 @@ pub struct Tests { pub test_gen_validate_memops: Option, pub test_gen_validate_registers: Option, pub test_gen_validate_flags: Option, + pub test_run_validate_cycles: Option, + pub test_run_validate_memops: Option, + pub test_run_validate_registers: Option, + pub test_run_validate_flags: Option, } #[derive(Debug, Deserialize)] From c4141f630a75c7e07ec7ba93e47d02a21c14fe56 Mon Sep 17 00:00:00 2001 From: dbalsom Date: Thu, 9 May 2024 09:29:54 -0400 Subject: [PATCH 07/11] add test CPU type command line argument --- ... Run_martypc__DEBUG___RUN_V20_CPU_TESTS_.xml} | 4 ++-- core/src/cpu_common/mod.rs | 16 ++++++++++++++++ core/src/machine.rs | 2 +- lib/frontend/config_toml_bpaf/src/lib.rs | 10 ++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) rename .idea/runConfigurations/{Run_martypc__DEBUG___RUN_CPU_TESTS_.xml => Run_martypc__DEBUG___RUN_V20_CPU_TESTS_.xml} (83%) diff --git a/.idea/runConfigurations/Run_martypc__DEBUG___RUN_CPU_TESTS_.xml b/.idea/runConfigurations/Run_martypc__DEBUG___RUN_V20_CPU_TESTS_.xml similarity index 83% rename from .idea/runConfigurations/Run_martypc__DEBUG___RUN_CPU_TESTS_.xml rename to .idea/runConfigurations/Run_martypc__DEBUG___RUN_V20_CPU_TESTS_.xml index b5e2d693..851259bb 100644 --- a/.idea/runConfigurations/Run_martypc__DEBUG___RUN_CPU_TESTS_.xml +++ b/.idea/runConfigurations/Run_martypc__DEBUG___RUN_V20_CPU_TESTS_.xml @@ -1,6 +1,6 @@ - -