Also made all LValues also RValues for brevity (so INC/DEC don't have to bound their destination arguments by both, but this is not required if it causes problems for some reason down the road).
384 lines
11 KiB
Rust
384 lines
11 KiB
Rust
use std::fmt::Debug;
|
|
use std::marker::PhantomData;
|
|
|
|
use emu::dos;
|
|
use emu::i8088::{Flags, RepPrefix, i8088};
|
|
use emu::operands::{Address, FarPtr, LValue, Operand, Reg, RValue};
|
|
use emu::pc::Bus;
|
|
|
|
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; // _carry used iff CF flag being updated
|
|
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, bus: &mut Bus, ss: u16, sp: &mut Reg, rel16: u16) {
|
|
let target = ip.read().wrapping_add(rel16);
|
|
push(bus, ss, sp, 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 = <FarPtr as RValue<u8>>::read(&FarPtr { bus: bus, segment: seg, offset: si.read() });
|
|
let dst = <FarPtr as RValue<u8>>::read(&FarPtr { 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 = <FarPtr as RValue<u16>>::read(&FarPtr { bus: bus, segment: seg, offset: si.read() });
|
|
let dst = <FarPtr as RValue<u16>>::read(&FarPtr { bus: bus, segment: es.read(), offset: di.read() });
|
|
|
|
cmp(flags, &dst, &src);
|
|
});
|
|
}
|
|
|
|
pub fn dec<T: Operand>(_width: PhantomData<T>, // Needed to disambiguate when dst is a FarPtr
|
|
flags: &mut Flags,
|
|
dst: &mut impl LValue<T>) {
|
|
let res = update_flags!(flags, T, dst.read().overflowing_sub(&T::one()), sf, zf, af, pf);
|
|
flags.of = res == T::HI_BIT_MASK - T::one();
|
|
dst.write(res);
|
|
}
|
|
|
|
pub fn inc<T: Operand>(_width: PhantomData<T>, // Needed to disambiguate when dst is a FarPtr
|
|
flags: &mut Flags,
|
|
dst: &mut impl LValue<T>) {
|
|
let res = update_flags!(flags, T, dst.read().overflowing_add(&T::one()), sf, zf, af, pf);
|
|
flags.of = res == T::HI_BIT_MASK;
|
|
dst.write(res);
|
|
}
|
|
|
|
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 ja(flags: &Flags, ip: &mut Reg, rel8: u16) {
|
|
if !flags.cf && !flags.zf {
|
|
ip.write(ip.read().wrapping_add(rel8));
|
|
}
|
|
}
|
|
|
|
pub fn jae(flags: &Flags, ip: &mut Reg, rel8: u16) {
|
|
if !flags.cf {
|
|
ip.write(ip.read().wrapping_add(rel8));
|
|
}
|
|
}
|
|
|
|
pub fn jb(flags: &Flags, ip: &mut Reg, rel8: u16) {
|
|
if flags.cf {
|
|
ip.write(ip.read().wrapping_add(rel8));
|
|
}
|
|
}
|
|
|
|
pub fn jbe(flags: &Flags, ip: &mut Reg, rel8: u16) {
|
|
if flags.cf || flags.zf {
|
|
ip.write(ip.read().wrapping_add(rel8));
|
|
}
|
|
}
|
|
|
|
pub fn jcxz(ip: &mut Reg, cx: &mut Reg, rel8: u16) {
|
|
if cx.read() == 0 {
|
|
ip.write(ip.read().wrapping_add(rel8));
|
|
}
|
|
}
|
|
|
|
pub fn jg(flags: &Flags, ip: &mut Reg, rel8: u16) {
|
|
if flags.zf && flags.sf == flags.of {
|
|
ip.write(ip.read().wrapping_add(rel8));
|
|
}
|
|
}
|
|
|
|
pub fn jge(flags: &Flags, ip: &mut Reg, rel8: u16) {
|
|
if flags.sf == flags.of {
|
|
ip.write(ip.read().wrapping_add(rel8));
|
|
}
|
|
}
|
|
|
|
pub fn jl(flags: &Flags, ip: &mut Reg, rel8: u16) {
|
|
if flags.sf != flags.of {
|
|
ip.write(ip.read().wrapping_add(rel8));
|
|
}
|
|
}
|
|
|
|
pub fn jle(flags: &Flags, ip: &mut Reg, rel8: u16) {
|
|
if flags.zf || flags.sf != flags.of {
|
|
ip.write(ip.read().wrapping_add(rel8));
|
|
}
|
|
}
|
|
|
|
pub fn jmp(ip: &mut Reg, rel: u16) {
|
|
ip.write(ip.read().wrapping_add(rel));
|
|
}
|
|
|
|
pub fn jno(flags: &Flags, ip: &mut Reg, rel8: u16) {
|
|
if !flags.of {
|
|
ip.write(ip.read().wrapping_add(rel8));
|
|
}
|
|
}
|
|
|
|
pub fn jns(flags: &Flags, ip: &mut Reg, rel8: u16) {
|
|
if !flags.sf {
|
|
ip.write(ip.read().wrapping_add(rel8));
|
|
}
|
|
}
|
|
|
|
pub fn jnz(flags: &Flags, ip: &mut Reg, rel8: u16) {
|
|
if !flags.zf {
|
|
ip.write(ip.read().wrapping_add(rel8));
|
|
}
|
|
}
|
|
|
|
pub fn jo(flags: &Flags, ip: &mut Reg, rel8: u16) {
|
|
if flags.of {
|
|
ip.write(ip.read().wrapping_add(rel8));
|
|
}
|
|
}
|
|
|
|
pub fn js(flags: &Flags, ip: &mut Reg, rel8: u16) {
|
|
if flags.sf {
|
|
ip.write(ip.read().wrapping_add(rel8));
|
|
}
|
|
}
|
|
|
|
pub fn jz(flags: &Flags, ip: &mut Reg, rel8: u16) {
|
|
if flags.zf {
|
|
ip.write(ip.read().wrapping_add(rel8));
|
|
}
|
|
}
|
|
|
|
pub fn lea(dst: &mut Reg, addr: &FarPtr) {
|
|
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(FarPtr { 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(FarPtr { 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 = <FarPtr as RValue<u8>>::read(&FarPtr { bus: bus, segment: seg, offset: si.read() });
|
|
let mut dst = FarPtr { bus: bus, segment: es.read(), offset: di.read() };
|
|
<FarPtr 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 = <FarPtr as RValue<u16>>::read(&FarPtr { bus: bus, segment: seg, offset: si.read() });
|
|
let mut dst = FarPtr { bus: bus, segment: es.read(), offset: di.read() };
|
|
<FarPtr as LValue<u16>>::write(&mut dst, src);
|
|
});
|
|
}
|
|
|
|
pub fn nop() {}
|
|
|
|
pub fn pop(bus: &mut Bus, ss: u16, sp: &mut Reg, dst: &mut Reg) {
|
|
let ptr = FarPtr { bus: bus, segment: ss, offset: sp.read() };
|
|
dst.write(ptr.read());
|
|
sp.write(sp.read() + 2);
|
|
}
|
|
|
|
pub fn push(bus: &mut Bus, ss: u16, sp: &mut Reg, val: u16) {
|
|
// XXX: Not checking for stack faults or anything
|
|
sp.write(sp.read() - 2);
|
|
let mut ptr = FarPtr { bus: bus, segment: ss, offset: sp.read() };
|
|
ptr.write(val);
|
|
}
|
|
|
|
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 = FarPtr { 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 = FarPtr { 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 = FarPtr { 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 = FarPtr { bus: bus, segment: es.read(), offset: di.read() };
|
|
dst.write(val.read());
|
|
});
|
|
}
|