emu: UNTESTED string ops, CMP instruction

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.
This commit is contained in:
2021-03-15 04:08:25 -07:00
parent 04348a816e
commit ae1f3da4b2
3 changed files with 337 additions and 22 deletions

View File

@@ -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,

View File

@@ -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<u8> for u8 {
fn read(&self) -> u8 {
*self

View File

@@ -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<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());
@@ -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<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),
@@ -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<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 {
@@ -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<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());
});
}