Likely to be a bugfest. There's a flags macro for now but unclear how useful it will turn out to be in practice. Flags in particular need testing. Also disappointing that I couldn't make instructions generic across u8/u16, especially for ordinary, non-string CMP. Need to look into this more to see if it can be done with num-traits or some other trickery.
287 lines
8.8 KiB
Rust
287 lines
8.8 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, 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<T: Debug + Eq>(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 cmpb(flags: &mut Flags, dst: &impl RValue<u8>, src: &impl RValue<u8>) {
|
|
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<u16>, src: &impl RValue<u16>) {
|
|
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 = <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() });
|
|
|
|
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 = <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() });
|
|
|
|
cmpw(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() };
|
|
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<u16>) {
|
|
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<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());
|
|
});
|
|
}
|