From 639e05edac94bf8a327486754ead6441fb5986db Mon Sep 17 00:00:00 2001 From: Jared Burce Date: Thu, 11 Feb 2021 06:29:57 -0800 Subject: [PATCH] basic emu skeleton and loop, CALL instruction & rel16 addr mode --- Cargo.toml | 2 +- src/bin/run88.rs | 14 ++++ src/emu/i8088.rs | 183 +++++++++++++++++++++++++++++++++++++++++++++++ src/emu/mod.rs | 4 ++ src/emu/pc.rs | 57 +++++++++++++++ src/lib.rs | 1 + 6 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 src/bin/run88.rs create mode 100644 src/emu/i8088.rs create mode 100644 src/emu/mod.rs create mode 100644 src/emu/pc.rs diff --git a/Cargo.toml b/Cargo.toml index 497793d..ad9ad13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,11 @@ version = "0.1.0" authors = ["Jared Burce "] [dependencies] +byteorder = "1.4.2" env_logger = "0.3" itertools = ">=0.4" log = "0.3" memmap = "~0.2" - gl = "0.10" gfx = "0.17" gfx_device_gl = "0.15" diff --git a/src/bin/run88.rs b/src/bin/run88.rs new file mode 100644 index 0000000..abf3a4a --- /dev/null +++ b/src/bin/run88.rs @@ -0,0 +1,14 @@ +use std::io::Read; + +extern crate vrtue; +use vrtue::emu::pc::PC; + +fn main() -> Result<(), std::io::Error> { + let filename = std::env::args().nth(1).expect("Need filename argument"); + let mut file = Vec::new(); + std::fs::File::open(filename)?.read_to_end(&mut file)?; + let mut pc = PC::new_with_com_file(&file); + pc.run(); + println!("{:?}", pc); + Ok(()) +} diff --git a/src/emu/i8088.rs b/src/emu/i8088.rs new file mode 100644 index 0000000..e9bff09 --- /dev/null +++ b/src/emu/i8088.rs @@ -0,0 +1,183 @@ +use std::fmt::{Debug, Formatter}; + +use super::byteorder::{ByteOrder, LittleEndian}; + +use emu::pc::Bus; + +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Debug, Default)] +pub struct i8088 { + // Data Registers + pub a: Reg16, + pub b: Reg16, + pub c: Reg16, + pub d: Reg16, + + // Index Registers + pub si: u16, // Source Index + pub di: u16, // Dest Index + pub bp: u16, // Base Pointer + pub sp: u16, // Stack Pointer + + // Segment Registers + pub cs: u16, // Code Segment + pub ds: u16, // Data Segment + pub es: u16, // Extra Segment + pub ss: u16, // Stack Segment + + // Pointer Register + pub ip: 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 { + pub fn call(cpu: &mut super::i8088, mem: &mut [u8], addr: i16) { + let target = cpu.ip.wrapping_add(addr as u16); + trace!("CALL 0x{:04X}", target); + cpu.push16(mem, cpu.ip); + cpu.ip = target; + } +} + +macro_rules! step { + (@code $cpu:ident, $bus:ident, ($name:ident, $cycles:literal, $arg:tt)) => { + step!(@$arg $name $cpu, $bus) + }; + + (@rel16 $name:ident $cpu:ident, $bus:ident) => { { + let mut buf = [0; 2]; + buf[0] = $cpu.next_ip($bus); + buf[1] = $cpu.next_ip($bus); + ops::$name($cpu, &mut $bus.ram, LittleEndian::read_i16(&buf)); + } }; + + // Entry Point + (($cpu:ident, $bus:ident) => { $( $code:literal => $impl:tt ),* } ) => { + { + let opcode = $cpu.next_ip($bus); + match(opcode) { + $( $code => step!(@code $cpu, $bus, $impl) )*, + _ => unimplemented!("opcode: {:02X}", opcode) + } + } + } +} + +pub const fn segoff_to_addr(segment: u16, offset: u16) -> usize { + let segaddr = (segment as usize) << 4; + segaddr + offset as usize +} + +impl i8088 { + pub fn run(&mut self, bus: &mut Bus) { + loop { + step!((self, bus) => { + 0xE8 => (call, 23, rel16) + }) + } + } + + fn push16(&mut self, mem: &mut [u8], val: u16) { + // XXX: Not checking for stack faults or anything + self.sp -= 2; + LittleEndian::write_u16(&mut mem[segoff_to_addr(self.ss, self.sp)..], val); + } + + fn next_ip<'a>(&mut self, bus: &Bus) -> u8 { + let eip = segoff_to_addr(self.cs, self.ip); + self.ip += 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] + } +} + + +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(()) + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub union Reg16 { + x: u16, + lh: [u8; 2], +} + +impl Debug for Reg16 { + fn fmt(&self, fmt: &mut Formatter) -> Result<(), std::fmt::Error> { + unsafe { self.x.fmt(fmt) } + } +} + +impl Default for Reg16 { + fn default() -> Self { Self { x: 0u16 } } +} diff --git a/src/emu/mod.rs b/src/emu/mod.rs new file mode 100644 index 0000000..d9a9281 --- /dev/null +++ b/src/emu/mod.rs @@ -0,0 +1,4 @@ +extern crate byteorder; + +pub mod pc; +pub mod i8088; diff --git a/src/emu/pc.rs b/src/emu/pc.rs new file mode 100644 index 0000000..b797797 --- /dev/null +++ b/src/emu/pc.rs @@ -0,0 +1,57 @@ +use emu::i8088::{i8088, segoff_to_addr}; + +use std::fmt::{Debug, Formatter}; + +const RAM_SIZE: usize = 128 * 1024; + +pub struct PC { + pub cpu: i8088, + bus: Bus, +} + +pub struct Bus { + pub ram: [u8; RAM_SIZE], + //io: +} + +impl Bus { + pub const RAM_LOCATION: usize = 0x0000_0000; + //const EGA_LOCATION: usize = 0x000A_0000; + + +} + +impl PC { + pub fn new_with_com_file(comfile: &[u8]) -> PC { + const LOAD_SEGMENT: u16 = 0x0050; + const LOAD_OFFSET: u16 = 0x0100; + const LOAD_ADDRESS: usize = segoff_to_addr(LOAD_SEGMENT, LOAD_OFFSET); + const LOAD_RAM_ADDRESS: usize = LOAD_ADDRESS - Bus::RAM_LOCATION; + assert!(comfile.len() <= 0xFF00, "COM file larger than spec"); + debug_assert!(LOAD_RAM_ADDRESS + comfile.len() <= RAM_SIZE, + "No memory for loading COM file"); + + let mut pc = PC { + cpu: { i8088 { cs: LOAD_SEGMENT, // COMs always uses Tiny memory model + ds: LOAD_SEGMENT, // COMs always uses Tiny memory model + es: LOAD_SEGMENT, // COMs always uses Tiny memory model + ss: LOAD_SEGMENT, // COMs always uses Tiny memory model + sp: 0xFFFE, + ip: LOAD_OFFSET, + ..i8088::default() } }, + bus: Bus { ram: [0; RAM_SIZE] } + }; + pc.bus.ram[LOAD_RAM_ADDRESS..LOAD_RAM_ADDRESS + comfile.len()].copy_from_slice(comfile); + pc + } + + pub fn run(&mut self) { + self.cpu.run(&mut self.bus); + } +} + +impl Debug for PC { + fn fmt(&self, fmt: &mut Formatter) -> Result<(), std::fmt::Error> { + self.cpu.fmt(fmt) + } +} diff --git a/src/lib.rs b/src/lib.rs index 9f82a03..b7f3cf6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ extern crate piston; pub mod arena; pub mod ega; +pub mod emu; pub mod scene; pub mod scenes; pub mod tile;