emu: wrap cpu regs in Cell. r16 addr mode.

r16 + modrm16 allowed cpu regs to alias. Wrap them in Cell to allow
this. We also create a Reg wrapper when passing regs so that we can
pass as &mut while simultaneously having other references to the same
reg.
This commit is contained in:
2021-03-10 07:09:59 -08:00
parent 49789c74f9
commit 71bcec1574
3 changed files with 104 additions and 87 deletions

View File

@@ -2,16 +2,16 @@ use emu::pc::Bus;
use emu::i8088::{i8088, read_hi, read_lo, segoff_to_addr};
pub fn interrupt(cpu: &mut i8088, bus: &mut Bus) {
let svc = read_hi(cpu.a);
let svc = read_hi(&cpu.a);
match svc {
0x09 => print_string(cpu, bus),
0x4C => exit(read_lo(cpu.a)),
0x4C => exit(read_lo(&cpu.a)),
_ => unimplemented!("dos service: AH={:02X}h\ncpu: {:#X?}", svc, cpu)
}
}
fn print_string(cpu: &i8088, bus: &Bus) {
let addr = segoff_to_addr(cpu.ds, cpu.d);
let addr = segoff_to_addr(cpu.ds.get(), cpu.d.get());
for byte in bus.ram[addr..].iter().take_while(|byte| **byte as char != '$') {
print!("{}", *byte as char);
}

View File

@@ -1,3 +1,4 @@
use std::cell::Cell;
use std::fmt::{Debug, Formatter};
use super::byteorder::{ByteOrder, LittleEndian};
@@ -5,28 +6,28 @@ use super::byteorder::{ByteOrder, LittleEndian};
use emu::pc::Bus;
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug, Default)]
#[derive(Clone, Debug, Default)]
pub struct i8088 {
// Data Registers
pub a: u16,
pub b: u16,
pub c: u16,
pub d: u16,
pub a: Cell<u16>,
pub b: Cell<u16>,
pub c: Cell<u16>,
pub d: Cell<u16>,
// Index Registers
pub si: u16, // Source Index
pub di: u16, // Dest Index
pub bp: u16, // Base Pointer
pub sp: u16, // Stack Pointer
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: u16, // Code Segment
pub ds: u16, // Data Segment
pub es: u16, // Extra Segment
pub ss: u16, // Stack Segment
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: u16,
pub ip: Cell<u16>,
// Status Register
pub flags: Flags,
@@ -52,22 +53,22 @@ pub struct Flags {
mod ops {
use emu::byteorder::{ByteOrder, LittleEndian};
use emu::dos;
use emu::i8088::{Bus, LValue, RValue, i8088, segoff_to_addr};
use emu::i8088::{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, *addr);
let addr = segoff_to_addr(cpu.ds.get(), *addr);
let val = bus.ram[addr];
println!("PEEK: @{:#X} = {:#X} ({})", addr, val, val);
}
pub fn call(ip: &mut u16, ss: u16, sp: &mut u16, mem: &mut [u8], addr: i16) {
let target = ip.wrapping_add(addr as u16);
push16(ss, sp, mem, *ip);
*ip = target;
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) {
@@ -81,20 +82,20 @@ mod ops {
dst.write(src.read());
}
pub fn pop16(ss: u16, sp: &mut u16, mem: &[u8]) -> u16 {
let val = LittleEndian::read_u16(&mem[segoff_to_addr(ss, *sp)..]);
*sp += 2;
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 u16, mem: &mut [u8], val: u16) {
pub fn push16(ss: u16, sp: &mut Reg, mem: &mut [u8], val: u16) {
// XXX: Not checking for stack faults or anything
*sp -= 2;
LittleEndian::write_u16(&mut mem[segoff_to_addr(ss, *sp)..], val);
sp.write(sp.read() - 2);
LittleEndian::write_u16(&mut mem[segoff_to_addr(ss.read(), sp.read())..], val);
}
pub fn ret(ip: &mut u16, ss: u16, sp: &mut u16, mem: &[u8]) {
*ip = pop16(ss, sp, mem);
pub fn ret(ip: &mut Reg, ss: u16, sp: &mut Reg, mem: &[u8]) {
ip.write(pop16(ss, sp, mem));
}
}
@@ -124,8 +125,24 @@ impl RValue<u16> for u16 {
}
}
pub struct Reg<'a> {
reg: &'a Cell<u16>
}
impl<'a> LValue<u16> for Reg<'a> {
fn write(&mut self, val: u16) {
self.reg.set(val);
}
}
impl<'a> RValue<u16> for Reg<'a> {
fn read(&self) -> u16 {
self.reg.get()
}
}
struct RegHi<'a> {
reg: &'a mut u16
reg: &'a Cell<u16>
}
impl<'a> LValue<u8> for RegHi<'a> {
@@ -136,12 +153,12 @@ impl<'a> LValue<u8> for RegHi<'a> {
impl<'a> RValue<u8> for RegHi<'a> {
fn read(&self) -> u8 {
read_hi(*self.reg)
read_hi(self.reg)
}
}
struct RegLo<'a> {
reg: &'a mut u16
reg: &'a Cell<u16>
}
impl<'a> LValue<u8> for RegLo<'a> {
@@ -152,7 +169,7 @@ impl<'a> LValue<u8> for RegLo<'a> {
impl<'a> RValue<u8> for RegLo<'a> {
fn read(&self) -> u8 {
read_lo(*self.reg)
read_lo(self.reg)
}
}
@@ -196,14 +213,14 @@ macro_rules! step {
} };
(@d8 $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { {
let d8 = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus);
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, &mut $cpu.ip, $bus);
buf[1] = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus);
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))
} };
@@ -220,57 +237,56 @@ macro_rules! step {
}
} };
(@r16 $cookie:tt, $cpu:expr, $bus:expr, ($modrm:ident, $modrm16:tt, $modrm8:tt)) => { {
(@r16 $cookie:tt, $cpu:expr, $bus:expr, ($modrm:ident, $modrm16:tt, $modrm8:tt)) => {
// TODO: Should these also be passed into the macro like the modrm specs?
let reg = match $modrm >> 3 & 0x7 {
0 => &mut $cpu.a,
1 => &mut $cpu.c,
2 => &mut $cpu.d,
3 => &mut $cpu.b,
4 => &mut $cpu.sp,
5 => &mut $cpu.bp,
6 => &mut $cpu.si,
7 => &mut $cpu.di,
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!()
};
step!(@arg $cookie, reg)
} };
};
(@r8 $cookie:tt, $cpu:expr, $bus:expr, ($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: &mut $cpu.a } ),
1 => step!(@arg $cookie, &mut RegHi { reg: &mut $cpu.c } ),
2 => step!(@arg $cookie, &mut RegHi { reg: &mut $cpu.d } ) ,
3 => step!(@arg $cookie, &mut RegHi { reg: &mut $cpu.b } ),
4 => step!(@arg $cookie, &mut RegLo { reg: &mut $cpu.a } ),
5 => step!(@arg $cookie, &mut RegLo { reg: &mut $cpu.c } ),
6 => step!(@arg $cookie, &mut RegLo { reg: &mut $cpu.d } ),
7 => step!(@arg $cookie, &mut RegLo { reg: &mut $cpu.b } ),
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, $modrm:tt) => {
step!(@arg $cookie, &mut $cpu.$reg);
step!(@arg $cookie, &mut Reg { reg: &$cpu.$reg });
};
(@reghi=$reg:ident $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => {
step!(@arg $cookie, &mut RegHi { reg: &mut $cpu.$reg });
step!(@arg $cookie, &mut RegHi { reg: &$cpu.$reg });
};
(@reglo=$reg:ident $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => {
step!(@arg $cookie, &mut RegLo { reg: &mut $cpu.$reg });
step!(@arg $cookie, &mut RegLo { reg: &$cpu.$reg });
};
(@regval=$reg:ident $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => {
step!(@arg $cookie, $cpu.$reg);
step!(@arg $cookie, $cpu.$reg.get());
};
(@rel16 $cookie:tt, $cpu:expr, $bus:expr, $modrm:tt) => { {
let mut buf = [0; 2];
buf[0] = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus);
buf[1] = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus);
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))
} };
@@ -303,17 +319,17 @@ macro_rules! step {
modrm8: $modrm8:tt
) => {
{
let opcode = i8088::next_ip($cpu.cs, &mut $cpu.ip, $bus);
let opcode = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus);
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, &mut $cpu.ip, $bus); )?
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, &mut $cpu.ip, $bus);
modrm = i8088::next_ip($cpu.cs.get(), &mut $cpu.ip, $bus);
step!(@group $cpu, $bus, (modrm, $modrm16, $modrm8), $code, $subcodes)
}
)?
@@ -329,30 +345,30 @@ pub const fn segoff_to_addr(segment: u16, offset: u16) -> usize {
segaddr + offset as usize
}
pub fn read_hi(val: u16) -> u8 {
pub fn read_hi(val: &Cell<u16>) -> u8 {
let mut buf = [0; 2];
LittleEndian::write_u16(&mut buf, val);
LittleEndian::write_u16(&mut buf, val.get());
buf[1] as u8
}
pub fn read_lo(val: u16) -> u8 {
pub fn read_lo(val: &Cell<u16>) -> u8 {
let mut buf = [0; 2];
LittleEndian::write_u16(&mut buf, val);
LittleEndian::write_u16(&mut buf, val.get());
buf[0] as u8
}
pub fn write_hi(reg: &mut u16, val: u8) {
pub fn write_hi(reg: &Cell<u16>, val: u8) {
let mut buf = [0; 2];
LittleEndian::write_u16(&mut buf, *reg);
LittleEndian::write_u16(&mut buf, reg.get());
buf[1] = val;
*reg = LittleEndian::read_u16(&buf);
reg.set(LittleEndian::read_u16(&buf))
}
pub fn write_lo(reg: &mut u16, val: u8) {
pub fn write_lo(reg: &Cell<u16>, val: u8) {
let mut buf = [0; 2];
LittleEndian::write_u16(&mut buf, *reg);
LittleEndian::write_u16(&mut buf, reg.get());
buf[1] = val;
*reg = LittleEndian::read_u16(&buf);
reg.set(LittleEndian::read_u16(&buf));
}
impl i8088 {
@@ -362,7 +378,7 @@ impl i8088 {
opcodes: {
0x60 => show[cpu] / 0, // Fake opcode for debugging
0x61 => peek[cpu, bus, d16] / 0, // Fake opcode for debugging
//0x8B _ => mov[r16, modrm16] / "2/12+",
0x8B _ => mov[r16, modrm16] / "2/12+",
0x8C: { 0x08 => mov[modrm16, reg=cs] / "2/13+", },
0x8E: { 0x18 => mov[reg=ds, modrm16] / "2/12+", },
0xB4 => mov[reghi=a, d8] / 4,
@@ -394,9 +410,9 @@ impl i8088 {
}
}
fn next_ip<'a>(cs: u16, ip: &mut u16, bus: &Bus) -> u8 {
let eip = segoff_to_addr(cs, *ip);
*ip += 1;
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

View File

@@ -1,3 +1,4 @@
use std::cell::Cell;
use emu::i8088::{i8088, segoff_to_addr};
use std::fmt::{Debug, Formatter};
@@ -32,12 +33,12 @@ impl PC {
"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,
cpu: { i8088 { cs: Cell::new(LOAD_SEGMENT), // COMs always uses Tiny memory model
ds: Cell::new(LOAD_SEGMENT), // COMs always uses Tiny memory model
es: Cell::new(LOAD_SEGMENT), // COMs always uses Tiny memory model
ss: Cell::new(LOAD_SEGMENT), // COMs always uses Tiny memory model
sp: Cell::new(0xFFFE),
ip: Cell::new(LOAD_OFFSET),
..i8088::default() } },
bus: Bus { ram: [0; RAM_SIZE] }
};