diff --git a/src/emu/i8088.rs b/src/emu/i8088.rs index b859455..ebeba2a 100644 --- a/src/emu/i8088.rs +++ b/src/emu/i8088.rs @@ -53,16 +53,14 @@ pub struct Flags { mod ops { use emu::byteorder::{ByteOrder, LittleEndian}; use emu::dos; - use emu::i8088::{Bus, LValue, Reg, RValue, i8088, segoff_to_addr}; + use emu::i8088::{Address, Bus, LValue, Reg, RValue, i8088, segoff_to_addr}; pub fn show(cpu: &mut i8088) { println!("{:#X?}", cpu); } - pub fn peek(cpu: &mut i8088, bus: &mut Bus, addr: &u16) { - let addr = segoff_to_addr(cpu.ds.get(), *addr); - let val = bus.ram[addr]; - println!("PEEK: @{:#X} = {:#X} ({})", addr, val, val); + pub fn peek(addr: &(impl Address + RValue)) { + 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) { @@ -82,6 +80,8 @@ mod ops { dst.write(src.read()); } + 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); @@ -107,6 +107,10 @@ pub trait RValue { fn read(&self) -> T; } +pub trait Address { + fn addr(&self) -> usize; +} + impl RValue for u8 { fn read(&self) -> u8 { *self @@ -129,13 +133,13 @@ pub struct Reg<'a> { reg: &'a Cell } -impl<'a> LValue for Reg<'a> { +impl LValue for Reg<'_> { fn write(&mut self, val: u16) { self.reg.set(val); } } -impl<'a> RValue for Reg<'a> { +impl RValue for Reg<'_> { fn read(&self) -> u16 { self.reg.get() } @@ -145,29 +149,52 @@ struct RegHi<'a> { reg: &'a Cell } -impl<'a> LValue for RegHi<'a> { +impl LValue for RegHi<'_> { fn write(&mut self, val: u8) { write_hi(&mut self.reg, val); } } -impl<'a> RValue for RegHi<'a> { +impl RValue for RegHi<'_> { fn read(&self) -> u8 { read_hi(self.reg) } } +struct Addr<'a> { + bus: &'a mut Bus, + addr: usize +} + +impl Address for Addr<'_> { + fn addr(&self) -> usize { + self.addr + } +} + +impl LValue for Addr<'_> { + fn write(&mut self, val: u8) { + self.bus.write(self.addr, val); + } +} + +impl RValue for Addr<'_> { + fn read(&self) -> u8 { + self.bus.read(self.addr) + } +} + struct RegLo<'a> { reg: &'a Cell } -impl<'a> LValue for RegLo<'a> { +impl LValue for RegLo<'_> { fn write(&mut self, val: u8) { write_lo(&mut self.reg, val); } } -impl<'a> RValue for RegLo<'a> { +impl RValue for RegLo<'_> { fn read(&self) -> u8 { read_lo(self.reg) } @@ -176,59 +203,64 @@ impl<'a> RValue for RegLo<'a> { macro_rules! step { // Base case: all args processed and ready to call op (@code ( $($done:tt)* ), - $cpu:expr, $bus:expr, $modrm:tt, $name:ident, $cycles:literal, + $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, $modrm:tt, $name:ident, $cycles:literal, + $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, $modrm, $name, $cycles, ($($rest $(= $restrhs)?)*) ), - $cpu, $bus, $modrm) + ( ($($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, $modrm:tt, $name:ident, $cycles:literal, $rest:tt ), - $arg:expr) => { - step!(@code ($($done)* $arg), $cpu, $bus, $modrm, $name, $cycles, $rest) + (@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, $modrm:tt, $name:ident, $cycles:literal, ( $($rest:tt)* ) ), + (@push ( $done:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt, $name:ident, $cycles:literal, ( $($rest:tt)* ) ), ( $($args:tt)* )) => { - step!(@code $done, $cpu, $bus, $modrm, $name, $cycles, ( $($args)* $($rest)* )) + step!(@code $done, $cpu, $bus, $prefix, $modrm, $name, $cycles, ( $($args)* $($rest)* )) }; // Argument Decoders - (@bus $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { { + (@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, $modrm:tt) => { { + (@cpu $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { step!(@arg $cookie, $cpu) - } }; + }; - (@d8 $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { { + (@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, $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_u16(&buf)) + (@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, $modrm:tt) => { + (@mem $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, $modrm:tt) => { step!(@arg $cookie, &mut $bus.ram) }; - (@modrm16 $cookie:tt, $cpu:expr, $bus:expr, + (@modrm16 $cookie:tt, $cpu:expr, $bus:expr, $prefix:tt, ($modrm_val:ident, { $($val:literal => $($args:ident = $argrhs:tt),*),*$(,)? }, $modrm8:tt)) => { { let modrm_val = $modrm_val & !0x38; match modrm_val { @@ -237,7 +269,9 @@ macro_rules! step { } } }; - (@r16 $cookie:tt, $cpu:expr, $bus:expr, ($modrm:ident, $modrm16:tt, $modrm8:tt)) => { + (@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 } ), @@ -252,7 +286,7 @@ macro_rules! step { }; }; - (@r8 $cookie:tt, $cpu:expr, $bus:expr, ($modrm:ident, $modrm16:tt, $modrm8:tt)) => { + (@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 } ), @@ -267,36 +301,41 @@ macro_rules! step { }; }; - (@reg=$reg:ident $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { + (@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, $modrm:tt) => { + (@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, $modrm:tt) => { + (@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, $modrm:tt) => { + (@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, $modrm:tt) => { { + (@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, ($modrm:ident, $modrm16:tt, $modrm8:tt), $code:literal, + (@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, ($modrm, $modrm16, $modrm8), $name, $cycles, ($($args $(= $argrhs)?)*)) ),*, + $( $subcode => step!(@code (), $cpu, $bus, $prefix, ($modrm, $modrm16, $modrm8), $name, $cycles, ($($args $(= $argrhs)?)*)) ),*, _ => unimplemented!("opcode: {:02X} {:02X}({:02X})\ncpu: {:#X?}", $code, $modrm, subcode, $cpu) } } }; @@ -319,22 +358,27 @@ macro_rules! step { modrm8: $modrm8:tt ) => { { - let opcode = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); + let mut segment = None; let modrm: u8; // Type ascription unnecessary but gives better err messages when missing $ext - 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, (modrm, $modrm16, $modrm8), $name, $cycles, ($($args $(= $argrhs)?)*)) - } - )? - $( $code => { - modrm = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus); - step!(@group $cpu, $bus, (modrm, $modrm16, $modrm8), $code, $subcodes) - } - )? - ),*, - _ => unimplemented!("opcode: {:02X}\ncpu: {:#X?}", opcode, $cpu) + '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; } } } @@ -376,11 +420,16 @@ impl i8088 { 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[cpu, bus, d16] / 0, // Fake opcode for debugging + 0x61 => peek[seg=ds, addr] / 0, // Fake opcode for debugging 0x8B _ => mov[r16, modrm16] / "2/12+", 0x8C: { 0x08 => mov[modrm16, reg=cs] / "2/13+", }, 0x8E: { 0x18 => mov[reg=ds, modrm16] / "2/12+", }, + 0x90 => nop[] / 3, 0xB4 => mov[reghi=a, d8] / 4, 0xBA => mov[reg=d, d16] / 4, 0xC3 => ret[reg=ip, regval=ss, reg=sp, mem] / 20, @@ -419,6 +468,17 @@ impl i8088 { // 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) + } } diff --git a/src/emu/pc.rs b/src/emu/pc.rs index f526326..465e7e0 100644 --- a/src/emu/pc.rs +++ b/src/emu/pc.rs @@ -19,7 +19,13 @@ impl Bus { pub const RAM_LOCATION: usize = 0x0000_0000; //const EGA_LOCATION: usize = 0x000A_0000; + pub fn read(&self, addr: usize) -> u8 { + self.ram[addr] + } + pub fn write(&mut self, addr: usize, val: u8) { + self.ram[addr] = val; + } } impl PC {