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,