diff --git a/src/emu/i8088.rs b/src/emu/i8088.rs index c5408fb..0889f19 100644 --- a/src/emu/i8088.rs +++ b/src/emu/i8088.rs @@ -38,21 +38,28 @@ pub struct i8088 { #[derive(Clone, Copy, Default)] pub struct Flags { - cf: bool, // 0: Carry Flag: 1=CY(Carry), 0=NC(No Carry) + pub 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) + pub 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) + pub 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) + pub zf: bool, // 6: Zero Flag: 1=ZR(Zero), 0=NZ(Not Zero) + pub sf: bool, // 7: Sign Flag: 1=NG(Negative), 0=PL(Positive) + pub tf: bool, // 8: Trap Flag + pub ie: bool, // 9: (Real name "IF") Interrupt Enable: 1=EI(Enable Interrupt), 0=DI(Disable Interrupt) + pub df: bool, // 10: Direction Flag: 1=DN(Down), 0=UP(Up) + pub of: bool, // 11: Overflow Flag: 1=OV(Overflow), 0=NV(Not Overflow) // bits 12-15 always 1 } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum RepPrefix { + None, // No Prefix + Equal, // REP/REPE/REPZ + NotEqual // REPNE/REPNZ +} + macro_rules! step { // Base case: all args processed and ready to call op (@code ( $($done:tt)* ), @@ -85,7 +92,9 @@ macro_rules! step { }; // Argument Decoders - (@addr $cookie:tt, $cpu:expr, $bus:expr, ($segment:ident, $prefix_loop:lifetime), $modrm:tt) => { { + (@addr $cookie:tt, $cpu:expr, $bus:expr, + ($segment:ident, $repeat:ident, $prefix_loop:lifetime), + $modrm:tt) => { { let a16 = i8088::next_ip16($cpu.cs.get(), &mut $cpu.ip, $bus); step!(@arg $cookie, &mut Addr { bus: $bus, segment: $segment.unwrap(), offset: a16 } ) } }; @@ -108,26 +117,36 @@ macro_rules! step { step!(@arg $cookie, &d16) } }; - (@displace0=($reg1:ident $(+ $reg2:ident)? ) $cookie:tt, $cpu:expr, $bus:expr, ($segment:ident, $prefix_loop:lifetime), $modrm:tt) => { + (@displace0=($reg1:ident $(+ $reg2:ident)? ) $cookie:tt, $cpu:expr, $bus:expr, + ($segment:ident, $repeat:ident, $prefix_loop:lifetime), + $modrm:tt) => { step!(@arg $cookie, &mut Addr { bus: $bus, segment: $segment.unwrap(), offset: $cpu.$reg1.get() $(+ $cpu.$reg2.get())? }) }; - (@displace8=($reg1:ident $(+ $reg2:ident)? ) $cookie:tt, $cpu:expr, $bus:expr, ($segment:ident, $prefix_loop:lifetime), $modrm:tt) => { { + (@displace8=($reg1:ident $(+ $reg2:ident)? ) $cookie:tt, $cpu:expr, $bus:expr, + ($segment:ident, $repeat:ident, $prefix_loop:lifetime), + $modrm:tt) => { { let d8 = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); step!(@arg $cookie, &mut Addr { bus: $bus, segment: $segment.unwrap(), offset: $cpu.$reg1.get() $(+ $cpu.$reg2.get())? + d8 as u16 }) } }; - (@displace16=($reg1:ident $(+ $reg2:ident)? ) $cookie:tt, $cpu:expr, $bus:expr, ($segment:ident, $prefix_loop:lifetime), $modrm:tt) => { { + (@displace16=($reg1:ident $(+ $reg2:ident)? ) $cookie:tt, $cpu:expr, $bus:expr, + ($segment:ident, $repeat:ident, $prefix_loop:lifetime), + $modrm:tt) => { { let d16 = i8088::next_ip16($cpu.cs.get(), &mut $cpu.ip, $bus); step!(@arg $cookie, &mut Addr { bus: $bus, segment: $segment.unwrap(), offset: $cpu.$reg1.get() $(+ $cpu.$reg2.get())? + d16 }) } }; + (@flags $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { + step!(@arg $cookie, &mut $cpu.flags) + }; + (@mem $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { step!(@arg $cookie, &mut $bus.ram) }; @@ -173,7 +192,11 @@ macro_rules! step { } } }; - (@prefix $cookie:tt, $cpu:expr, $bus:expr, ($segment:ident, $prefix_loop:lifetime), $modrm:tt) => { continue $prefix_loop; }; + (@prefix $cookie:tt, $cpu:expr, $bus:expr, + ($segment:ident, $repeat:ident, $prefix_loop:lifetime), + $modrm:tt) => { + continue $prefix_loop; + }; (@r16 $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, ($modrm_val:ident, $modrm:tt, $modrm16:tt, $modrm8:tt)) => { // TODO: Should these also be passed into the macro like the modrm specs? @@ -228,13 +251,42 @@ macro_rules! step { step!(@arg $cookie, LittleEndian::read_i16(&buf)) } }; - (@seg=$seg:ident $cookie:tt, $cpu:expr, $bus:expr, ($segment:ident, $prefix_loop:lifetime), $modrm:tt) => { { + (@rep $cookie:tt, $cpu:expr, $bus:expr, + ($segment:ident, $repeat:ident, $prefix_loop:lifetime), + $modrm:tt) => { { + step!(@arg $cookie, $repeat) + } }; + + (@rep=$mode:ident $cookie:tt, $cpu:expr, $bus:expr, + ($segment:ident, $repeat:ident, $prefix_loop:lifetime), + $modrm:tt) => { { + $repeat = RepPrefix::$mode; + step!(@arg $cookie) + } }; + + (@rep $cookie:tt, $cpu:expr, $bus:expr, + ($segment:ident, $repeat:ident, $prefix_loop:lifetime), + $modrm:tt) => { { + step!(@arg $cookie, $repeat) + } }; + + (@seg=$seg:ident $cookie:tt, $cpu:expr, $bus:expr, + ($segment:ident, $repeat:ident, $prefix_loop:lifetime), + $modrm:tt) => { { $segment = $segment.or(Some($cpu.$seg.get())); step!(@arg $cookie) } }; + (@seg $cookie:tt, $cpu:expr, $bus:expr, + ($segment:ident, $repeat:ident, $prefix_loop:lifetime), + $modrm:tt) => { { + step!(@arg $cookie, $segment.unwrap()) + } }; + // Group Decoder - (@group $cpu:expr, $bus:expr, $prefix:tt, ($modrm_val:ident, $modrm:tt, $modrm16:tt, $modrm8:tt), $code:literal, + (@group $cpu:expr, $bus:expr, $prefix:tt, + ($modrm_val:ident, $modrm:tt, $modrm16:tt, $modrm8:tt), + $code:literal, { $( $subcode:literal => $name:ident[$($args:ident $(= $argrhs:tt)?),*] / $cycles:literal),*$(,)? } ) => { { let subcode = $modrm_val & 0x38; @@ -264,6 +316,7 @@ macro_rules! step { ) => { { let mut segment = None; + let mut repeat = RepPrefix::None; let modrm_val: u8; // Type ascription unnecessary but gives better err messages when missing $ext 'prefix_loop: loop { let opcode = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); @@ -271,12 +324,12 @@ macro_rules! step { $( $( $code => { $( let $ext = (); /* No-op just to trigger expansion. */ modrm_val = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); )? - step!(@code (), $cpu, $bus, (segment, 'prefix_loop), (modrm_val, $modrm, $modrm16, $modrm8), $name, $cycles, ($($args $(= $argrhs)?)*)) + step!(@code (), $cpu, $bus, (segment, repeat, 'prefix_loop), (modrm_val, $modrm, $modrm16, $modrm8), $name, $cycles, ($($args $(= $argrhs)?)*)) } )? $( $code => { modrm_val = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); - step!(@group $cpu, $bus, (segment, 'prefix_loop), (modrm_val, $modrm, $modrm16, $modrm8), $code, $subcodes) + step!(@group $cpu, $bus, (segment, repeat, 'prefix_loop), (modrm_val, $modrm, $modrm16, $modrm8), $code, $subcodes) } )? ),*, @@ -295,13 +348,22 @@ impl i8088 { step!((self, bus) => opcodes: { 0x26 => nop[seg=es, prefix] / 2, - 0x2e => nop[seg=cs, prefix] / 2, + 0x2E => nop[seg=cs, prefix] / 2, 0x36 => nop[seg=ss, prefix] / 2, - 0x3e => nop[seg=ds, prefix] / 2, + 0x38 _ => cmpb[flags, modrm8, r8] / "3/24+", + 0x39 _ => cmpw[flags, modrm16, r16] / "3/24+", + 0x3A _ => cmpb[flags, r8, modrm8] / "3/24+", + 0x3B _ => cmpw[flags, r16, modrm16] / "3/24+", + 0x3C => cmpb[flags, reglo=a, d8] / "3/24+", + 0x3D => cmpw[flags, reg=a, d16] / "3/24+", + 0x3E => nop[seg=ds, prefix] / 2, 0x60 => show[cpu] / 0, // Fake opcode for debugging 0x61 => peek[seg=ds, addr] / 0, // Fake opcode for debugging 0x62 _ => assert[modrm8, d8] / 0, // Fake opcode for debugging 0x63 _ => assert[modrm16, d16] / 0, // Fake opcode for debugging + 0x80: { 0x38 => cmpb[flags, modrm8, d8] / "4/23+", }, + 0x81: { 0x38 => cmpw[flags, modrm16, d16] / "4/23+", }, + //0x83: { 0x38 => cmp?[flags, modrm16, d8] / "4/23+", }, 0x88 _ => mov[modrm8, r8] / "2/13+", 0x89 _ => mov[modrm16, r16] / "2/13+", 0x8A _ => mov[r8, modrm8] / "2/12+", @@ -320,6 +382,16 @@ impl i8088 { 0xA1 => mov[reg=a, addr] / 14, 0xA2 => mov[addr, reglo=a] / 14, 0xA3 => mov[addr, reg=a] / 14, + 0xA4 => movsb[flags, bus, rep, reg=c, seg=ds, seg, reg=si, reg=es, reg=di] / "18/9+17n", + 0xA5 => movsw[flags, bus, rep, reg=c, seg=ds, seg, reg=si, reg=es, reg=di] / "26/9+25n", + 0xA6 => cmpsb[flags, bus, rep, reg=c, seg=ds, seg, reg=si, reg=es, reg=di] / "22/9+22n", + 0xA7 => cmpsw[flags, bus, rep, reg=c, seg=ds, seg, reg=si, reg=es, reg=di] / "30/9+30n", + 0xAA => stosb[flags, bus, rep, reg=c, reg=es, reg=di, reglo=a] / "11/9+10n", + 0xAB => stosw[flags, bus, rep, reg=c, reg=es, reg=di, reg=a] / "15/9+14n", + 0xAC => lodsb[flags, bus, rep, reg=c, seg=ds, seg, reg=si, reglo=a] / "12/9+13n", + 0xAD => lodsw[flags, bus, rep, reg=c, seg=ds, seg, reg=si, reg=a] / "16/9+17n", + 0xAE => scasb[flags, bus, rep, reg=c, reg=es, reg=di, reglo=a] / "15/9+15n", + 0xAF => scasw[flags, bus, rep, reg=c, reg=es, reg=di, reg=a] / "19/9+19n", 0xB0 => mov[reglo=a, d8] / 4, 0xB1 => mov[reglo=c, d8] / 4, 0xB2 => mov[reglo=d, d8] / 4, @@ -339,6 +411,8 @@ impl i8088 { 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, + 0xF2 => nop[rep=NotEqual, prefix] / 0, // REPNE/REPNZ + 0xF3 => nop[rep=Equal, prefix] / 0, // REP/REPE/REPZ }, modrm: { 0x00 => seg=ds, displace0=(b+si) / M 7, diff --git a/src/emu/operands.rs b/src/emu/operands.rs index bb3c8c5..76b681b 100644 --- a/src/emu/operands.rs +++ b/src/emu/operands.rs @@ -16,6 +16,22 @@ pub trait Address { fn addr(&self) -> usize; } +pub trait HiBit { + fn hi_bit(&self) -> bool; +} + +impl HiBit for u8 { + fn hi_bit(&self) -> bool { + self >> 7 == 1 + } +} + +impl HiBit for u16 { + fn hi_bit(&self) -> bool { + self >> 15 == 1 + } +} + impl RValue for u8 { fn read(&self) -> u8 { *self diff --git a/src/emu/operations.rs b/src/emu/operations.rs index 9f52bc4..6df06bb 100644 --- a/src/emu/operations.rs +++ b/src/emu/operations.rs @@ -1,12 +1,83 @@ use std::fmt::Debug; use emu::byteorder::{ByteOrder, LittleEndian}; + use emu::dos; -use emu::i8088::i8088; -use emu::operands::{Addr, Address, LValue, Reg, RValue}; +use emu::i8088::{Flags, RepPrefix, i8088}; +use emu::operands::{Addr, Address, HiBit, LValue, Reg, RValue}; use emu::pc::Bus; use emu::util::segoff_to_addr; +macro_rules! string_op { + ( ($type:ty, + $flags:ident, + $rep:ident, + $cx:ident + $(, si=$si:expr)? + $(, di=$di:expr)? + $(, zf=$zf:expr)? ), + $code:tt + ) => { + let stride = std::mem::size_of::<$type>() as u16; + if $rep == RepPrefix::None { + $code; + if $flags.df { + $($si.write($si.read() - stride);)? + $($di.write($di.read() - stride);)? + } else { + $($si.write($si.read() + stride);)? + $($di.write($di.read() + stride);)? + } + } else { + while $cx.read() != 0 { + $cx.write($cx.read() - 1); + $code; + if $flags.df { + $($si.write($si.read() - stride);)? + $($di.write($di.read() - stride);)? + } else { + $($si.write($si.read() + stride);)? + $($di.write($di.read() + stride);)? + } + $( match ($rep, $zf) { + (RepPrefix::Equal, true) => (), + (RepPrefix::Equal, false) => break, + (RepPrefix::NotEqual, true) => break, + (RepPrefix::NotEqual, false) => (), + _ => (), + } )? + } + } + } +} + +// Based on SUB/CMP, others will vary! +// Decide if this can realistically be generalized +macro_rules! update_flags { + // Base Case + (@recur $flags:expr, $dst:ident, $carry:ident) => {}; + + (@af $flags:expr, $dst:ident, $carry:ident) => { /* XXX: Adjust/AuxCarry esoteric? (mostly for BCD?), might not need */ }; + (@cf $flags:expr, $dst:ident, $carry:ident) => { $flags.cf = $carry }; + (@of $flags:expr, $dst:ident, $carry:ident) => { compile_error!("compute flags.of outside macro"); }; + (@pf $flags:expr, $dst:ident, $carry:ident) => { /* XXX: parity looks esoteric, might not need */ }; + (@sf $flags:expr, $dst:ident, $carry:ident) => { $flags.sf = $dst.hi_bit(); }; + (@zf $flags:expr, $dst:ident, $carry:ident) => { $flags.zf = $dst == 0; }; + + // Recursive step + (@recur $flags:expr, $dst:ident, $carry:ident, $next:ident $(, $rest:ident)* ) => { + update_flags!(@$next $flags, $dst, $carry); + update_flags!(@recur $flags, $dst, $carry $(, $rest)*); + }; + + // Entry Point + ($flags:expr, $eval:expr $(, $flagnames:ident)* ) => { { + let (dst, carry) = $eval; + update_flags!(@recur $flags, dst, carry $(, $flagnames)*); + dst + } } +} + pub fn assert(loc: &impl RValue, val: &impl RValue) { assert_eq!(loc.read(), val.read(), "ASSERT instruction failed: {:#2X?} != {:#2X?}", loc.read(), val.read()); @@ -27,6 +98,54 @@ pub fn call(ip: &mut Reg, ss: u16, sp: &mut Reg, mem: &mut [u8], addr: i16) { ip.write(target); } +pub fn cmpb(flags: &mut Flags, dst: &impl RValue, src: &impl RValue) { + let (dst, src) = (dst.read(), src.read()); + let after = update_flags!(flags, dst.overflowing_sub(src), af, cf, pf, sf, zf); + flags.of = 0 != 0x80 & // In the sign bit... + (src ^ dst) & // ...operands have different signs... + (dst ^ after); // ...and destination sign-bit changed +} + +pub fn cmpw(flags: &mut Flags, dst: &impl RValue, src: &impl RValue) { + let (dst, src) = (dst.read(), src.read()); + let after = update_flags!(flags, dst.overflowing_sub(src), af, cf, pf, sf, zf); + flags.of = 0 != 0x8000 & // In the sign bit... + (src ^ dst) & // ...operands have different signs... + (dst ^ after); // ...and destination sign-bit changed +} + +pub fn cmpsb(flags: &mut Flags, + bus: &mut Bus, + rep: RepPrefix, + cx: &mut Reg, + seg: u16, + si: &mut Reg, + es: &mut Reg, + di: &mut Reg) { + string_op!((u8, flags, rep, cx, di=di, zf=flags.zf), { + let src = >::read(&Addr { bus: bus, segment: seg, offset: si.read() }); + let dst = >::read(&Addr { bus: bus, segment: es.read(), offset: di.read() }); + + cmpb(flags, &dst, &src); + }); +} + +pub fn cmpsw(flags: &mut Flags, + bus: &mut Bus, + rep: RepPrefix, + cx: &mut Reg, + seg: u16, + si: &mut Reg, + es: &mut Reg, + di: &mut Reg) { + string_op!((u16, flags, rep, cx, di=di, zf=flags.zf), { + let src = >::read(&Addr { bus: bus, segment: seg, offset: si.read() }); + let dst = >::read(&Addr { bus: bus, segment: es.read(), offset: di.read() }); + + cmpw(flags, &dst, &src); + }); +} + pub fn int(cpu: &mut i8088, bus: &mut Bus, num: &u8) { match num { 0x21 => dos::interrupt(cpu, bus), @@ -38,10 +157,64 @@ pub fn lea(dst: &mut Reg, addr: &Addr) { dst.write(addr.offset) } +pub fn lodsb(flags: &Flags, + bus: &mut Bus, + rep: RepPrefix, + cx: &mut Reg, + seg: u16, + si: &mut Reg, + al: &mut impl LValue) { + string_op!((u8, flags, rep, cx, si=si), { + al.write(Addr { bus: bus, segment: seg, offset: si.read() }.read()); + }); +} + +pub fn lodsw(flags: &Flags, + bus: &mut Bus, + rep: RepPrefix, + cx: &mut Reg, + seg: u16, + si: &mut Reg, + ax: &mut impl LValue) { + string_op!((u16, flags, rep, cx, si=si), { + ax.write(Addr { bus: bus, segment: seg, offset: si.read() }.read()); + }); +} + pub fn mov(dst: &mut impl LValue, src: &impl RValue) { dst.write(src.read()); } +pub fn movsb(flags: &Flags, + bus: &mut Bus, + rep: RepPrefix, + cx: &mut Reg, + seg: u16, + si: &mut Reg, + es: &Reg, + di: &mut Reg) { + string_op!((u8, flags, rep, cx, si=si, di=di), { + let src = >::read(&Addr { bus: bus, segment: seg, offset: si.read() }); + let mut dst = Addr { bus: bus, segment: es.read(), offset: di.read() }; + >::write(&mut dst, src); + }); +} + +pub fn movsw(flags: &Flags, + bus: &mut Bus, + rep: RepPrefix, + cx: &mut Reg, + seg: u16, + si: &mut Reg, + es: &Reg, + di: &mut Reg) { + string_op!((u16, flags, rep, cx, si=si, di=di), { + let src = >::read(&Addr { bus: bus, segment: seg, offset: si.read() }); + let mut dst = Addr { bus: bus, segment: es.read(), offset: di.read() }; + >::write(&mut dst, src); + }); +} + pub fn nop() {} pub fn pop16(ss: u16, sp: &mut Reg, mem: &[u8]) -> u16 { @@ -59,3 +232,55 @@ pub fn push16(ss: u16, sp: &mut Reg, mem: &mut [u8], val: u16) { pub fn ret(ip: &mut Reg, ss: u16, sp: &mut Reg, mem: &[u8]) { ip.write(pop16(ss, sp, mem)); } + +pub fn scasb(flags: &mut Flags, + bus: &mut Bus, + rep: RepPrefix, + cx: &mut Reg, + es: &Reg, + di: &mut Reg, + needle: &impl RValue) { + string_op!((u8, flags, rep, cx, di=di, zf=flags.zf), { + let elem = Addr { bus: bus, segment: es.read(), offset: di.read() }; + cmpb(flags, &elem, needle); + }); +} + +pub fn scasw(flags: &mut Flags, + bus: &mut Bus, + rep: RepPrefix, + cx: &mut Reg, + es: &Reg, + di: &mut Reg, + needle: &impl RValue) { + string_op!((u16, flags, rep, cx, di=di, zf=flags.zf), { + let elem = Addr { bus: bus, segment: es.read(), offset: di.read() }; + cmpw(flags, &elem, needle); + }); +} + +pub fn stosb(flags: &Flags, + bus: &mut Bus, + rep: RepPrefix, + cx: &mut Reg, + es: &Reg, + di: &mut Reg, + val: &impl RValue) { + string_op!((u8, flags, rep, cx, di=di), { + let mut dst = Addr { bus: bus, segment: es.read(), offset: di.read() }; + dst.write(val.read()); + }); +} + +pub fn stosw(flags: &Flags, + bus: &mut Bus, + rep: RepPrefix, + cx: &mut Reg, + es: &Reg, + di: &mut Reg, + val: &impl RValue) { + string_op!((u16, flags, rep, cx, di=di), { + let mut dst = Addr { bus: bus, segment: es.read(), offset: di.read() }; + dst.write(val.read()); + }); +}