use std::cell::Cell; use std::fmt::{Debug, Formatter}; use super::byteorder::{ByteOrder, LittleEndian}; use emu::operands::{Addr, Reg, RegHi, RegLo}; use emu::operations as ops; use emu::pc::Bus; use emu::util::segoff_to_addr; #[allow(non_camel_case_types)] #[derive(Clone, Debug, Default)] pub struct i8088 { // Data Registers pub a: Cell, pub b: Cell, pub c: Cell, pub d: Cell, // Index Registers pub si: Cell, // Source Index pub di: Cell, // Dest Index pub bp: Cell, // Base Pointer pub sp: Cell, // Stack Pointer // Segment Registers pub cs: Cell, // Code Segment pub ds: Cell, // Data Segment pub es: Cell, // Extra Segment pub ss: Cell, // Stack Segment // Pointer Register pub ip: Cell, // Status Register pub flags: Flags, } #[derive(Clone, Copy, Default)] pub struct Flags { 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) // 3: Reserved 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) // bits 12-15 always 1 } macro_rules! step { // Base case: all args processed and ready to call op (@code ( $($done:tt)* ), $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt, $name:ident, $cycles:literal, ()) => { ops::$name($($done),*); }; // Inductive case: decode next arg to be placed in list (@code ( $($done:tt)* ), $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt, $name:ident, $cycles:literal, ($next:ident $(= $nextrhs:ident)? $($rest:ident $(= $restrhs:ident)?)*)) => { step!(@$next$(= $nextrhs)? // "cookie" tt to be passed thru to @arg so we have all the args for the recursive step ( ($($done)*), $cpu, $bus, $prefix, $modrm, $name, $cycles, ($($rest $(= $restrhs)?)*) ), $cpu, $bus, $prefix, $modrm) }; // accept an argument from a decoder and recur to look for next arg (@arg ( ($($done:tt)*), $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt, $name:ident, $cycles:literal, $rest:tt ) $(, $arg:expr)?) => { step!(@code ($($done)* $($arg)?), $cpu, $bus, $prefix, $modrm, $name, $cycles, $rest) }; // decoder deferring to sub-decoders, push them onto the decoder stack to process next (@push ( $done:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt, $name:ident, $cycles:literal, ( $($rest:tt)* ) ), ( $($args:tt)* )) => { step!(@code $done, $cpu, $bus, $prefix, $modrm, $name, $cycles, ( $($args)* $($rest)* )) }; // Argument Decoders (@addr $cookie:tt, $cpu:expr, $bus:expr, ($segment:ident, $prefix_loop:lifetime), $modrm:tt) => { { let a16 = i8088::next_ip16($cpu.cs.get(), &mut $cpu.ip, $bus); let addr = segoff_to_addr($segment.unwrap(), a16); step!(@arg $cookie, &mut Addr { bus: $bus, addr: addr } ) } }; (@bus $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { step!(@arg $cookie, $bus) }; (@cpu $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { step!(@arg $cookie, $cpu) }; (@d8 $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { { let d8 = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); step!(@arg $cookie, &d8) } }; (@d16 $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { { let d16 = i8088::next_ip16($cpu.cs.get(), &mut $cpu.ip, $bus); step!(@arg $cookie, &d16) } }; (@mem $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { step!(@arg $cookie, &mut $bus.ram) }; (@modrm16 $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, ($modrm_val:ident, { $($val:literal => $($args:ident = $argrhs:tt),* / $mode:ident $cycles:literal),*$(,)? }, $modrm8:tt)) => { { let modrm_val = $modrm_val & !0x38; match modrm_val { $( $val => step!(@push $cookie, ($($args = $argrhs)* ) ) ),*, _ => unimplemented!("modrm: {:02X}({:02X})\ncpu: {:#X?}", $modrm_val, modrm_val, $cpu) } } }; (@modrm8 $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, ($modrm_val:ident, $modrm16:tt, { $($val:literal => $($args:ident = $argrhs:tt),* / $mode:ident $cycles:literal),*$(,)? } )) => { { let modrm_val = $modrm_val & !0x38; match modrm_val { $( $val => step!(@push $cookie, ($($args = $argrhs)* ) ) ),*, _ => unimplemented!("modrm: {:02X}({:02X})\ncpu: {:#X?}", $modrm_val, modrm_val, $cpu) } } }; (@prefix $cookie:tt, $cpu:expr, $bus:expr, ($segment:ident, $prefix_loop:lifetime), $modrm:tt) => { continue $prefix_loop; }; (@r16 $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, ($modrm:ident, $modrm16:tt, $modrm8:tt)) => { // TODO: Should these also be passed into the macro like the modrm specs? match $modrm >> 3 & 0x7 { 0 => step!(@arg $cookie, &mut Reg { reg: &$cpu.a } ), 1 => step!(@arg $cookie, &mut Reg { reg: &$cpu.c } ), 2 => step!(@arg $cookie, &mut Reg { reg: &$cpu.d } ), 3 => step!(@arg $cookie, &mut Reg { reg: &$cpu.b } ), 4 => step!(@arg $cookie, &mut Reg { reg: &$cpu.sp } ), 5 => step!(@arg $cookie, &mut Reg { reg: &$cpu.bp } ), 6 => step!(@arg $cookie, &mut Reg { reg: &$cpu.si } ), 7 => step!(@arg $cookie, &mut Reg { reg: &$cpu.di } ), _ => unreachable!() }; }; (@r8 $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, ($modrm:ident, $modrm16:tt, $modrm8:tt)) => { // TODO: Should these also be passed into the macro like the modrm specs? match $modrm >> 3 & 0x7 { 0 => step!(@arg $cookie, &mut RegHi { reg: &$cpu.a } ), 1 => step!(@arg $cookie, &mut RegHi { reg: &$cpu.c } ), 2 => step!(@arg $cookie, &mut RegHi { reg: &$cpu.d } ), 3 => step!(@arg $cookie, &mut RegHi { reg: &$cpu.b } ), 4 => step!(@arg $cookie, &mut RegLo { reg: &$cpu.a } ), 5 => step!(@arg $cookie, &mut RegLo { reg: &$cpu.c } ), 6 => step!(@arg $cookie, &mut RegLo { reg: &$cpu.d } ), 7 => step!(@arg $cookie, &mut RegLo { reg: &$cpu.b } ), _ => unreachable!() }; }; (@reg=$reg:ident $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { step!(@arg $cookie, &mut Reg { reg: &$cpu.$reg }); }; (@reghi=$reg:ident $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { step!(@arg $cookie, &mut RegHi { reg: &$cpu.$reg }); }; (@reglo=$reg:ident $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { step!(@arg $cookie, &mut RegLo { reg: &$cpu.$reg }); }; (@regval=$reg:ident $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { step!(@arg $cookie, $cpu.$reg.get()); }; (@rel16 $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { { let mut buf = [0; 2]; buf[0] = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); buf[1] = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); step!(@arg $cookie, LittleEndian::read_i16(&buf)) } }; (@seg=$seg:ident $cookie:tt, $cpu:expr, $bus:expr, ($segment:ident, $prefix_loop:lifetime), $modrm:tt) => { { $segment = $segment.or(Some($cpu.$seg.get())); step!(@arg $cookie) } }; // Group Decoder (@group $cpu:expr, $bus:expr, $prefix:tt, ($modrm:ident, $modrm16:tt, $modrm8:tt), $code:literal, { $( $subcode:literal => $name:ident[$($args:ident $(= $argrhs:ident)?),*] / $cycles:literal),*$(,)? } ) => { { let subcode = $modrm & 0x38; match subcode { $( $subcode => step!(@code (), $cpu, $bus, $prefix, ($modrm, $modrm16, $modrm8), $name, $cycles, ($($args $(= $argrhs)?)*)) ),*, _ => unimplemented!("opcode: {:02X} {:02X}({:02X})\ncpu: {:#X?}", $code, $modrm, subcode, $cpu) } } }; // Entry Point (($cpu:expr, $bus:expr) => opcodes: { $( $code:literal // Non-group opcodes $( $($ext:pat)? => $name:ident[$($args:ident $(= $argrhs:ident)?),*] / $cycles:literal )? // Group opcodes $( : $subcodes:tt )? ),* $(,)? }, modrm16: $modrm16:tt, modrm8: $modrm8:tt ) => { { let mut segment = None; let modrm: 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); match(opcode) { $( $( $code => { $( let $ext = (); /* No-op just to trigger expansion. */ modrm = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); )? step!(@code (), $cpu, $bus, (segment, 'prefix_loop), (modrm, $modrm16, $modrm8), $name, $cycles, ($($args $(= $argrhs)?)*)) } )? $( $code => { modrm = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); step!(@group $cpu, $bus, (segment, 'prefix_loop), (modrm, $modrm16, $modrm8), $code, $subcodes) } )? ),*, _ => unimplemented!("opcode: {:02X}\ncpu: {:#X?}", opcode, $cpu) } // Only prefixes loop and they do so with an explicit continue statement break; } } } } impl i8088 { pub fn run(&mut self, bus: &mut Bus) { loop { step!((self, bus) => opcodes: { 0x26 => nop[seg=es, prefix] / 2, 0x2e => nop[seg=cs, prefix] / 2, 0x36 => nop[seg=ss, prefix] / 2, 0x3e => nop[seg=ds, prefix] / 2, 0x60 => show[cpu] / 0, // Fake opcode for debugging 0x61 => peek[seg=ds, addr] / 0, // Fake opcode for debugging 0x88 _ => mov[modrm8, r8] / "2/13+", 0x89 _ => mov[modrm16, r16] / "2/13+", 0x8A _ => mov[r8, modrm8] / "2/12+", 0x8B _ => mov[r16, modrm16] / "2/12+", 0x8C: { 0x00 => mov[modrm16, reg=es] / "2/13+", 0x08 => mov[modrm16, reg=cs] / "2/13+", 0x10 => mov[modrm16, reg=ss] / "2/13+", 0x18 => mov[modrm16, reg=ds] / "2/13+", }, 0x8E: { 0x00 => mov[reg=es, modrm16] / "2/12+", 0x08 => mov[reg=cs, modrm16] / "2/12+", 0x10 => mov[reg=ss, modrm16] / "2/12+", 0x18 => mov[reg=ds, modrm16] / "2/12+", }, 0x90 => nop[] / 3, 0xA0 => mov[reglo=a, addr] / 14, 0xA1 => mov[reg=a, addr] / 14, 0xA2 => mov[addr, reglo=a] / 14, 0xA3 => mov[addr, reg=a] / 14, 0xB0 => mov[reglo=a, d8] / 4, 0xB1 => mov[reglo=c, d8] / 4, 0xB2 => mov[reglo=d, d8] / 4, 0xB3 => mov[reglo=b, d8] / 4, 0xB4 => mov[reghi=a, d8] / 4, 0xB5 => mov[reghi=c, d8] / 4, 0xB6 => mov[reghi=d, d8] / 4, 0xB7 => mov[reghi=b, d8] / 4, 0xB8 => mov[reg=a, d16] / 4, 0xB9 => mov[reg=c, d16] / 4, 0xBA => mov[reg=d, d16] / 4, 0xBB => mov[reg=b, d16] / 4, 0xBC => mov[reg=sp, d16] / 4, 0xBD => mov[reg=bp, d16] / 4, 0xBE => mov[reg=si, d16] / 4, 0xBF => mov[reg=di, d16] / 4, 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, }, modrm16: { 0xC0 => reg=a / R 0, 0xC1 => reg=c / R 0, 0xC2 => reg=d / R 0, 0xC3 => reg=b / R 0, 0xC4 => reg=sp / R 0, 0xC5 => reg=bp / R 0, 0xC6 => reg=si / R 0, 0xC7 => reg=di / R 0, }, modrm8: { 0xC0 => reglo=a / R 0, 0xC1 => reglo=c / R 0, 0xC2 => reglo=d / R 0, 0xC3 => reglo=b / R 0, 0xC4 => reghi=a / R 0, 0xC5 => reghi=c / R 0, 0xC6 => reghi=d / R 0, 0xC7 => reghi=b / R 0, }) } } fn next_ip<'a>(cs: u16, ip: &mut Cell, bus: &Bus) -> u8 { let eip = segoff_to_addr(cs, ip.get()); ip.set(ip.get() + 1); // We'll assume cpu is always executing in RAM. Also assume the // IP doesn't reach the end of the segment (My guess is that it // should loop within the segment if it did) bus.ram[eip - Bus::RAM_LOCATION] } fn next_ip16<'a>(cs: u16, ip: &mut Cell, bus: &Bus) -> u16 { let eip = segoff_to_addr(cs, ip.get()); ip.set(ip.get() + 2); // We'll assume cpu is always executing in RAM. Also assume the // IP doesn't reach the end of the segment (My guess is that it // should loop within the segment if it did) let buf = &bus.ram[eip - Bus::RAM_LOCATION .. eip + 2 - Bus::RAM_LOCATION]; LittleEndian::read_u16(buf) } } impl From for Flags { fn from(flags: u16) -> Self { Self { cf: flags & 1 != 0, pf: flags & 1 << 2 != 0, af: flags & 1 << 4 != 0, zf: flags & 1 << 6 != 0, sf: flags & 1 << 7 != 0, tf: flags & 1 << 8 != 0, ie: flags & 1 << 9 != 0, df: flags & 1 << 10 != 0, of: flags & 1 << 11 != 0, } } } impl From for u16 { fn from(flags: Flags) -> Self { 0b1111_0000_0010_1010 // Not sure what all reserved bits should be, but it shouldn't matter | (flags.cf as u16) | (flags.pf as u16) << 2 | (flags.af as u16) << 4 | (flags.zf as u16) << 6 | (flags.sf as u16) << 7 | (flags.tf as u16) << 8 | (flags.ie as u16) << 9 | (flags.df as u16) << 10 | (flags.of as u16) << 11 } } impl Debug for Flags { fn fmt(&self, fmt: &mut Formatter) -> Result<(), std::fmt::Error> { use std::fmt::Write; fmt.write_str("[ ")?; for flag in [ (self.cf, "CF "), (self.pf, "PF "), (self.af, "AF "), (self.zf, "ZF "), (self.sf, "SF "), (self.tf, "TF "), (self.ie, "IF "), (self.df, "DF "), (self.of, "OF ") ].iter() { if flag.0 { fmt.write_str(flag.1)? }; } fmt.write_char(']')?; Ok(()) } }