Files
vrtue/src/emu/operations.rs

279 lines
8.5 KiB
Rust

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<T: Operand + Debug>(loc: &impl RValue<T>, val: &impl RValue<T>) {
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<u8>)) {
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<T: Operand>(flags: &mut Flags, dst: &impl RValue<T>, src: &impl RValue<T>) {
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 = <Addr as RValue<u8>>::read(&Addr { bus: bus, segment: seg, offset: si.read() });
let dst = <Addr as RValue<u8>>::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 = <Addr as RValue<u16>>::read(&Addr { bus: bus, segment: seg, offset: si.read() });
let dst = <Addr as RValue<u16>>::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<u8>) {
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<u16>) {
string_op!((u16, flags, rep, cx, si=si), {
ax.write(Addr { bus: bus, segment: seg, offset: si.read() }.read());
});
}
pub fn mov<T>(dst: &mut impl LValue<T>, src: &impl RValue<T>) {
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 = <Addr as RValue<u8>>::read(&Addr { bus: bus, segment: seg, offset: si.read() });
let mut dst = Addr { bus: bus, segment: es.read(), offset: di.read() };
<Addr as LValue<u8>>::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 = <Addr as RValue<u16>>::read(&Addr { bus: bus, segment: seg, offset: si.read() });
let mut dst = Addr { bus: bus, segment: es.read(), offset: di.read() };
<Addr as LValue<u16>>::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<u8>) {
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<u16>) {
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<u8>) {
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<u16>) {
string_op!((u16, flags, rep, cx, di=di), {
let mut dst = Addr { bus: bus, segment: es.read(), offset: di.read() };
dst.write(val.read());
});
}