use std::fmt::Debug; use emu::byteorder::{ByteOrder, LittleEndian}; use emu::dos; use emu::i8088::{Flags, RepPrefix, i8088}; use emu::operands::{Addr, Address, LValue, Operand, 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, $t:ty, $dst:ident, $carry:ident) => {}; (@af $flags:expr, $t:ty, $dst:ident, $carry:ident) => { /* XXX: Adjust/AuxCarry esoteric? (mostly for BCD?), might not need */ }; (@cf $flags:expr, $t:ty, $dst:ident, $carry:ident) => { $flags.cf = $carry }; (@of $flags:expr, $t:ty, $dst:ident, $carry:ident) => { compile_error!("compute flags.of outside macro"); }; (@pf $flags:expr, $t:ty, $dst:ident, $carry:ident) => { /* XXX: parity looks esoteric, might not need */ }; (@sf $flags:expr, $t:ty, $dst:ident, $carry:ident) => { $flags.sf = $dst.hi_bit(); }; (@zf $flags:expr, $t:ty, $dst:ident, $carry:ident) => { $flags.zf = $dst == <$t>::zero(); }; // Recursive step (@recur $flags:expr, $t:ty, $dst:ident, $carry:ident, $next:ident $(, $rest:ident)* ) => { update_flags!(@$next $flags, $t, $dst, $carry); update_flags!(@recur $flags, $t, $dst, $carry $(, $rest)*); }; // Entry Point ($flags:expr, $t:ty, $eval:expr $(, $flagnames:ident)* ) => { { let (dst, carry) = $eval; update_flags!(@recur $flags, $t, 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()); println!("ASSERT pass: {:#2X?} == {:#2X?}", loc.read(), val.read()); } pub fn show(cpu: &mut i8088) { println!("{:#X?}", cpu); } pub fn peek(addr: &(impl Address + RValue)) { println!("PEEK: @{:#X} = {:#X} ({})", addr.addr(), addr.read(), addr.read()); } pub fn call(ip: &mut Reg, ss: u16, sp: &mut Reg, mem: &mut [u8], addr: i16) { let target = ip.read().wrapping_add(addr as u16); push16(ss, sp, mem, ip.read()); ip.write(target); } pub fn cmp(flags: &mut Flags, dst: &impl RValue, src: &impl RValue) { let (dst, src) = (dst.read(), src.read()); let after = update_flags!(flags, T, dst.overflowing_sub(&src), af, cf, pf, sf, zf); flags.of = T::zero() != T::HI_BIT_MASK & // In the (maybe) 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() }); cmp(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() }); cmp(flags, &dst, &src); }); } 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 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 { let val = LittleEndian::read_u16(&mem[segoff_to_addr(ss.read(), sp.read())..]); sp.write(sp.read() + 2); val } pub fn push16(ss: u16, sp: &mut Reg, mem: &mut [u8], val: u16) { // XXX: Not checking for stack faults or anything sp.write(sp.read() - 2); LittleEndian::write_u16(&mut mem[segoff_to_addr(ss.read(), sp.read())..], val); } 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() }; cmp(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() }; cmp(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()); }); }