Files
vrtue/src/emu/i8088.rs

535 lines
16 KiB
Rust

use std::cell::Cell;
use std::fmt::{Debug, Formatter};
use super::byteorder::{ByteOrder, LittleEndian};
use emu::pc::Bus;
#[allow(non_camel_case_types)]
#[derive(Clone, Debug, Default)]
pub struct i8088 {
// Data Registers
pub a: Cell<u16>,
pub b: Cell<u16>,
pub c: Cell<u16>,
pub d: Cell<u16>,
// Index Registers
pub si: Cell<u16>, // Source Index
pub di: Cell<u16>, // Dest Index
pub bp: Cell<u16>, // Base Pointer
pub sp: Cell<u16>, // Stack Pointer
// Segment Registers
pub cs: Cell<u16>, // Code Segment
pub ds: Cell<u16>, // Data Segment
pub es: Cell<u16>, // Extra Segment
pub ss: Cell<u16>, // Stack Segment
// Pointer Register
pub ip: Cell<u16>,
// 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
}
mod ops {
use emu::byteorder::{ByteOrder, LittleEndian};
use emu::dos;
use emu::i8088::{Address, Bus, LValue, Reg, RValue, i8088, segoff_to_addr};
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 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 mov<T>(dst: &mut impl LValue<T>, src: &impl RValue<T>) {
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);
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 trait LValue<T> {
fn write(&mut self, val: T);
}
pub trait RValue<T> {
fn read(&self) -> T;
}
pub trait Address {
fn addr(&self) -> usize;
}
impl RValue<u8> for u8 {
fn read(&self) -> u8 {
*self
}
}
impl LValue<u16> for u16 {
fn write(&mut self, val: u16) {
*self = val;
}
}
impl RValue<u16> for u16 {
fn read(&self) -> u16 {
*self
}
}
pub struct Reg<'a> {
reg: &'a Cell<u16>
}
impl LValue<u16> for Reg<'_> {
fn write(&mut self, val: u16) {
self.reg.set(val);
}
}
impl RValue<u16> for Reg<'_> {
fn read(&self) -> u16 {
self.reg.get()
}
}
struct RegHi<'a> {
reg: &'a Cell<u16>
}
impl LValue<u8> for RegHi<'_> {
fn write(&mut self, val: u8) {
write_hi(&mut self.reg, val);
}
}
impl RValue<u8> 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<u8> for Addr<'_> {
fn write(&mut self, val: u8) {
self.bus.write(self.addr, val);
}
}
impl RValue<u8> for Addr<'_> {
fn read(&self) -> u8 {
self.bus.read(self.addr)
}
}
struct RegLo<'a> {
reg: &'a Cell<u16>
}
impl LValue<u8> for RegLo<'_> {
fn write(&mut self, val: u8) {
write_lo(&mut self.reg, val);
}
}
impl RValue<u8> for RegLo<'_> {
fn read(&self) -> u8 {
read_lo(self.reg)
}
}
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),*),*$(,)? }, $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)
}
} };
(@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;
}
}
}
}
pub const fn segoff_to_addr(segment: u16, offset: u16) -> usize {
let segaddr = (segment as usize) << 4;
segaddr + offset as usize
}
pub fn read_hi(val: &Cell<u16>) -> u8 {
let mut buf = [0; 2];
LittleEndian::write_u16(&mut buf, val.get());
buf[1] as u8
}
pub fn read_lo(val: &Cell<u16>) -> u8 {
let mut buf = [0; 2];
LittleEndian::write_u16(&mut buf, val.get());
buf[0] as u8
}
pub fn write_hi(reg: &Cell<u16>, val: u8) {
let mut buf = [0; 2];
LittleEndian::write_u16(&mut buf, reg.get());
buf[1] = val;
reg.set(LittleEndian::read_u16(&buf))
}
pub fn write_lo(reg: &Cell<u16>, val: u8) {
let mut buf = [0; 2];
LittleEndian::write_u16(&mut buf, reg.get());
buf[1] = val;
reg.set(LittleEndian::read_u16(&buf));
}
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
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,
0xCD => int[cpu, bus, d8] / 71,
0xE8 => call[reg=ip, regval=ss, reg=sp, mem, rel16] / 23,
},
modrm16: {
0xC0 => reg=a,
0xC1 => reg=c,
0xC2 => reg=d,
0xC3 => reg=b,
0xC4 => reg=sp,
0xC5 => reg=bp,
0xC6 => reg=si,
0xC7 => reg=di,
},
modrm8: {
0xC0 => reglo=a,
0xC1 => reglo=c,
0xC2 => reglo=d,
0xC3 => reglo=b,
0xC4 => reghi=a,
0xC5 => reghi=c,
0xC6 => reghi=d,
0xC7 => reghi=b,
})
}
}
fn next_ip<'a>(cs: u16, ip: &mut Cell<u16>, 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<u16>, 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<u16> 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<Flags> 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(())
}
}