use std::fmt::{Debug, Formatter}; use super::byteorder::{ByteOrder, LittleEndian}; use emu::pc::Bus; #[allow(non_camel_case_types)] #[derive(Clone, Copy, Debug, Default)] pub struct i8088 { // Data Registers pub a: u16, pub b: u16, pub c: u16, pub d: u16, // Index Registers pub si: u16, // Source Index pub di: u16, // Dest Index pub bp: u16, // Base Pointer pub sp: u16, // Stack Pointer // Segment Registers pub cs: u16, // Code Segment pub ds: u16, // Data Segment pub es: u16, // Extra Segment pub ss: u16, // Stack Segment // Pointer Register pub ip: u16, // Status Register pub flags: Flags, } #[derive(Clone, Copy, Default)] pub struct Flags { cf: bool, // 0: Carry Flag: 1=CY(Carry), 0=NC(No Carry) // 1: Reserved pf: bool, // 2: Parity Flag: 1=PE(Even), 0=PO(Odd) // 3: Reserved af: bool, // 4: Adjust Flag: 1=AC(Aux Carry), 0=NA(No Aux Carry) // 5: Reserved zf: bool, // 6: Zero Flag: 1=ZR(Zero), 0=NZ(Not Zero) sf: bool, // 7: Sign Flag: 1=NG(Negative), 0=PL(Positive) tf: bool, // 8: Trap Flag ie: bool, // 9: (Real name "IF") Interrupt Enable: 1=EI(Enable Interrupt), 0=DI(Disable Interrupt) df: bool, // 10: Direction Flag: 1=DN(Down), 0=UP(Up) of: bool, // 11: Overflow Flag: 1=OV(Overflow), 0=NV(Not Overflow) // bits 12-15 always 1 } mod ops { use emu::byteorder::{ByteOrder, LittleEndian}; use emu::dos; use emu::i8088::{Bus, LValue, RValue, i8088, segoff_to_addr}; pub fn call(ip: &mut u16, ss: u16, sp: &mut u16, mem: &mut [u8], addr: i16) { let target = ip.wrapping_add(addr as u16); push16(ss, sp, mem, *ip); *ip = target; } pub fn int(cpu: &mut i8088, bus: &mut Bus, num: &u8) { match num { 0x21 => dos::interrupt(cpu, bus), _ => unimplemented!("interrupt: {:02X}\ncpu: {:#X?}", num, cpu) } } pub fn mov(dst: &mut impl LValue, src: &impl RValue) { dst.write(src.read()); } pub fn pop16(ss: u16, sp: &mut u16, mem: &[u8]) -> u16 { let val = LittleEndian::read_u16(&mem[segoff_to_addr(ss, *sp)..]); *sp += 2; val } pub fn push16(ss: u16, sp: &mut u16, mem: &mut [u8], val: u16) { // XXX: Not checking for stack faults or anything *sp -= 2; LittleEndian::write_u16(&mut mem[segoff_to_addr(ss, *sp)..], val); } pub fn ret(ip: &mut u16, ss: u16, sp: &mut u16, mem: &[u8]) { *ip = pop16(ss, sp, mem); } } pub trait LValue { fn write(&mut self, val: T); } pub trait RValue { fn read(&self) -> T; } impl RValue for u8 { fn read(&self) -> u8 { *self } } impl LValue for u16 { fn write(&mut self, val: u16) { *self = val; } } impl RValue for u16 { fn read(&self) -> u16 { *self } } struct RegHi<'a> { reg: &'a mut u16 } impl<'a> LValue for RegHi<'a> { fn write(&mut self, val: u8) { write_hi(&mut self.reg, val); } } impl<'a> RValue for RegHi<'a> { fn read(&self) -> u8 { read_hi(*self.reg) } } macro_rules! step { // Base case: all args processed and ready to call op (@code ( $($args:tt)* ), $cpu:expr, $bus:expr, $name:ident, $cycles:literal, ()) => { ops::$name($($args),*); }; // Inductive case: decode next arg to be placed in list (@code ( $($args:tt)* ), $cpu:expr, $bus:expr, $name:ident, $cycles:literal, ($next:ident $(= $nextrhs:ident)? $($rest:ident $(= $restrhs:ident)?)*)) => { step!(@$next$(= $nextrhs)? ( ($($args)*), $cpu, $bus, $name, $cycles, ($($rest $(= $restrhs)?)*) ), $cpu, $bus) }; // accept an argument from a decoder and recur to look for next arg (@arg ( ($($args:tt)*), $cpu:expr, $bus:expr, $name:ident, $cycles:literal, ( $($rest:tt)* ) ), $arg:expr) => { step!(@code ($($args)* $arg), $cpu, $bus, $name, $cycles, ( $($rest)* )) }; // Argument Decoders (@bus $cookie:tt, $cpu:expr, $bus:expr) => { { step!(@arg $cookie, $bus) } }; (@cpu $cookie:tt, $cpu:expr, $bus:expr) => { { step!(@arg $cookie, $cpu) } }; (@d8 $cookie:tt, $cpu:expr, $bus:expr) => { { let d8 = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus); step!(@arg $cookie, &d8) } }; (@d16 $cookie:tt, $cpu:expr, $bus:expr) => { { let mut buf = [0; 2]; buf[0] = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus); buf[1] = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus); step!(@arg $cookie, &LittleEndian::read_u16(&buf)) } }; (@mem $cookie:tt, $cpu:expr, $bus:expr) => { step!(@arg $cookie, &mut $bus.ram) }; (@reg=$reg:ident $cookie:tt, $cpu:expr, $bus:expr) => { step!(@arg $cookie, &mut $cpu.$reg); }; (@reghi=$reg:ident $cookie:tt, $cpu:expr, $bus:expr) => { step!(@arg $cookie, &mut RegHi { reg: &mut $cpu.$reg }); }; (@regval=$reg:ident $cookie:tt, $cpu:expr, $bus:expr) => { step!(@arg $cookie, $cpu.$reg); }; (@rel16 $cookie:tt, $cpu:expr, $bus:expr) => { { let mut buf = [0; 2]; buf[0] = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus); buf[1] = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus); step!(@arg $cookie, LittleEndian::read_i16(&buf)) } }; // Entry Point (($cpu:expr, $bus:expr) => { $( $code:literal => $name:ident[$($args:ident $(= $argrhs:ident)?),*] / $cycles:literal ),*$(,)? } ) => { { let opcode = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus); match(opcode) { $( $code => step!(@code (), $cpu, $bus, $name, $cycles, ($($args $(= $argrhs)?)*)) ),*, _ => unimplemented!("opcode: {:02X}\ncpu: {:#X?}", opcode, $cpu) } } } } pub const fn segoff_to_addr(segment: u16, offset: u16) -> usize { let segaddr = (segment as usize) << 4; segaddr + offset as usize } pub fn read_hi(val: u16) -> u8 { let mut buf = [0; 2]; LittleEndian::write_u16(&mut buf, val); buf[1] as u8 } pub fn read_lo(val: u16) -> u8 { let mut buf = [0; 2]; LittleEndian::write_u16(&mut buf, val); buf[0] as u8 } pub fn write_hi(reg: &mut u16, val: u8) { let mut buf = [0; 2]; LittleEndian::write_u16(&mut buf, *reg); buf[1] = val; *reg = LittleEndian::read_u16(&buf); } pub fn write_lo(reg: &mut u16, val: u8) { let mut buf = [0; 2]; LittleEndian::write_u16(&mut buf, *reg); buf[1] = val; *reg = LittleEndian::read_u16(&buf); } impl i8088 { pub fn run(&mut self, bus: &mut Bus) { loop { step!((self, bus) => { 0xB4 => mov[reghi=a, d8] / 4, 0xBA => mov[reg=d, d16] / 4, 0xC3 => ret[reg=ip, regval=ss, reg=sp, mem] / 20, 0xCD => int[cpu, bus, d8] / 71, 0xE8 => call[reg=ip, regval=ss, reg=sp, mem, rel16] / 23, }) } } fn next_ip<'a>(cs: u16, ip: &mut u16, bus: &Bus) -> u8 { let eip = segoff_to_addr(cs, *ip); *ip += 1; // We'll assume cpu is always executing in RAM. Also assume the // IP doesn't reach the end of the segment (My guess is that it // should loop within the segment if it did) bus.ram[eip - Bus::RAM_LOCATION] } } impl From for Flags { fn from(flags: u16) -> Self { Self { cf: flags & 1 != 0, pf: flags & 1 << 2 != 0, af: flags & 1 << 4 != 0, zf: flags & 1 << 6 != 0, sf: flags & 1 << 7 != 0, tf: flags & 1 << 8 != 0, ie: flags & 1 << 9 != 0, df: flags & 1 << 10 != 0, of: flags & 1 << 11 != 0, } } } impl From for u16 { fn from(flags: Flags) -> Self { 0b1111_0000_0010_1010 // Not sure what all reserved bits should be, but it shouldn't matter | (flags.cf as u16) | (flags.pf as u16) << 2 | (flags.af as u16) << 4 | (flags.zf as u16) << 6 | (flags.sf as u16) << 7 | (flags.tf as u16) << 8 | (flags.ie as u16) << 9 | (flags.df as u16) << 10 | (flags.of as u16) << 11 } } impl Debug for Flags { fn fmt(&self, fmt: &mut Formatter) -> Result<(), std::fmt::Error> { use std::fmt::Write; fmt.write_str("[ ")?; for flag in [ (self.cf, "CF "), (self.pf, "PF "), (self.af, "AF "), (self.zf, "ZF "), (self.sf, "SF "), (self.tf, "TF "), (self.ie, "IF "), (self.df, "DF "), (self.of, "OF ") ].iter() { if flag.0 { fmt.write_str(flag.1)? }; } fmt.write_char(']')?; Ok(()) } }