basic emu skeleton and loop, CALL instruction & rel16 addr mode
This commit is contained in:
@@ -4,11 +4,11 @@ version = "0.1.0"
|
|||||||
authors = ["Jared Burce <jaredr@gmail.com>"]
|
authors = ["Jared Burce <jaredr@gmail.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
byteorder = "1.4.2"
|
||||||
env_logger = "0.3"
|
env_logger = "0.3"
|
||||||
itertools = ">=0.4"
|
itertools = ">=0.4"
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
memmap = "~0.2"
|
memmap = "~0.2"
|
||||||
|
|
||||||
gl = "0.10"
|
gl = "0.10"
|
||||||
gfx = "0.17"
|
gfx = "0.17"
|
||||||
gfx_device_gl = "0.15"
|
gfx_device_gl = "0.15"
|
||||||
|
|||||||
14
src/bin/run88.rs
Normal file
14
src/bin/run88.rs
Normal file
@@ -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(())
|
||||||
|
}
|
||||||
183
src/emu/i8088.rs
Normal file
183
src/emu/i8088.rs
Normal file
@@ -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<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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 } }
|
||||||
|
}
|
||||||
4
src/emu/mod.rs
Normal file
4
src/emu/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
extern crate byteorder;
|
||||||
|
|
||||||
|
pub mod pc;
|
||||||
|
pub mod i8088;
|
||||||
57
src/emu/pc.rs
Normal file
57
src/emu/pc.rs
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ extern crate piston;
|
|||||||
|
|
||||||
pub mod arena;
|
pub mod arena;
|
||||||
pub mod ega;
|
pub mod ega;
|
||||||
|
pub mod emu;
|
||||||
pub mod scene;
|
pub mod scene;
|
||||||
pub mod scenes;
|
pub mod scenes;
|
||||||
pub mod tile;
|
pub mod tile;
|
||||||
|
|||||||
Reference in New Issue
Block a user