618 lines
22 KiB
Rust
618 lines
22 KiB
Rust
use std::borrow::Borrow;
|
|
use std::cell::RefCell;
|
|
use std::collections::HashMap;
|
|
use std::ffi::CStr;
|
|
use std::fs::File;
|
|
use std::io::BufRead;
|
|
use std::io::BufReader;
|
|
use std::io::Read;
|
|
use std::rc::Rc;
|
|
use std::*;
|
|
|
|
use crate::device::console::ConsoleDevice;
|
|
use crate::device::null::NullDevice;
|
|
use crate::device::system::SystemDevice;
|
|
use crate::device::*;
|
|
use crate::isa::Icode;
|
|
use crate::memory::*;
|
|
use crate::stack::arrs::ArrayStack;
|
|
use crate::stack::pop::PopStack;
|
|
use crate::stack::*;
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum UxnError {
|
|
StackError(StackError),
|
|
MemoryError(MemoryError),
|
|
DeviceError(DeviceError),
|
|
ExecutionLimit(u16),
|
|
Break,
|
|
ArithmeticOverflow,
|
|
ArithmeticUnderflow,
|
|
DivisionByZero,
|
|
}
|
|
|
|
impl From<StackError> for UxnError {
|
|
fn from(e: StackError) -> UxnError {
|
|
UxnError::StackError(e)
|
|
}
|
|
}
|
|
|
|
impl From<MemoryError> for UxnError {
|
|
fn from(e: MemoryError) -> UxnError {
|
|
UxnError::MemoryError(e)
|
|
}
|
|
}
|
|
|
|
impl From<DeviceError> for UxnError {
|
|
fn from(e: DeviceError) -> UxnError {
|
|
UxnError::DeviceError(e)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Uxn {
|
|
pub memory: Rc<RefCell<TrivialMemory>>,
|
|
// Note: Using Rc so we can start with many NullDevs and replace them
|
|
pub devices: [Rc<RefCell<dyn Device>>; 16],
|
|
pub pc: u16, // Program counter
|
|
pub clock: u64, // Executed instructions count
|
|
pub wst: Rc<RefCell<dyn Stack>>, // Data stack
|
|
pub rst: Rc<RefCell<dyn Stack>>, // Return stack pointer
|
|
pub symbols: HashMap<u16, String>, // Symbol table
|
|
}
|
|
|
|
impl Uxn {
|
|
pub fn new() -> Uxn {
|
|
let mut uxn = Uxn {
|
|
memory: Rc::new(RefCell::new(TrivialMemory::new())),
|
|
devices: [
|
|
Rc::new(RefCell::new(SystemDevice::new())), // #00
|
|
Rc::new(RefCell::new(ConsoleDevice::new())), // #01
|
|
Rc::new(RefCell::new(NullDevice::new())), // #02
|
|
Rc::new(RefCell::new(NullDevice::new())), // #03
|
|
Rc::new(RefCell::new(NullDevice::new())), // #04
|
|
Rc::new(RefCell::new(NullDevice::new())), // #05
|
|
Rc::new(RefCell::new(NullDevice::new())), // #06
|
|
Rc::new(RefCell::new(NullDevice::new())), // #07
|
|
Rc::new(RefCell::new(NullDevice::new())), // #08
|
|
Rc::new(RefCell::new(NullDevice::new())), // #09
|
|
Rc::new(RefCell::new(NullDevice::new())), // #0a
|
|
Rc::new(RefCell::new(NullDevice::new())), // #0b
|
|
Rc::new(RefCell::new(NullDevice::new())), // #0c
|
|
Rc::new(RefCell::new(NullDevice::new())), // #0d
|
|
Rc::new(RefCell::new(NullDevice::new())), // #0e
|
|
Rc::new(RefCell::new(NullDevice::new())), // #0f
|
|
],
|
|
pc: 0x0100,
|
|
clock: 0,
|
|
wst: Rc::new(RefCell::new(ArrayStack::new())),
|
|
rst: Rc::new(RefCell::new(ArrayStack::new())),
|
|
symbols: HashMap::new(),
|
|
};
|
|
uxn.symbols.insert(0x0100u16, "main".into());
|
|
uxn
|
|
}
|
|
|
|
pub fn of1(program: &[u8]) -> Uxn {
|
|
let mut vm = Uxn::new();
|
|
let mut pc = vm.pc;
|
|
for icode in program.iter() {
|
|
vm.sta1(pc, *icode).unwrap();
|
|
pc += 1;
|
|
}
|
|
vm
|
|
}
|
|
|
|
pub fn load_rom(&mut self, file: File) -> Result<(), UxnError> {
|
|
let reader = BufReader::new(file);
|
|
|
|
let mut i = 0x0100u16;
|
|
for b in reader.bytes() {
|
|
self.sta1(i, b.unwrap())?;
|
|
i += 1;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn load_symbols(&mut self, file: File) -> Result<(), io::Error> {
|
|
let mut reader: BufReader<File> = BufReader::new(file);
|
|
|
|
loop {
|
|
let mut addr_buff = [0u8; 2];
|
|
match reader.read_exact(&mut addr_buff) {
|
|
Ok(_) => (),
|
|
Err(_) => break,
|
|
}
|
|
let addr = u16::from_le_bytes(addr_buff);
|
|
|
|
let mut sym_buff: Vec<u8> = Vec::new();
|
|
reader.read_until(0u8, &mut sym_buff)?;
|
|
let label = CStr::from_bytes_with_nul(sym_buff.as_slice())
|
|
.unwrap()
|
|
.to_str()
|
|
.unwrap()
|
|
.to_string();
|
|
|
|
self.symbols.insert(addr, label);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn is_halted(&mut self) -> bool {
|
|
self.dei1(0x0f).unwrap() != 0
|
|
}
|
|
|
|
pub fn is_tracing(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
pub fn debug(&mut self) {
|
|
let wst = self.wst.clone();
|
|
let rst = self.rst.clone();
|
|
|
|
print!(
|
|
"<symbols> #{:04X}\n<clock> #{:04X}\n<pc> #{:04X}\n<data stack>",
|
|
self.symbols.len(),
|
|
self.clock,
|
|
self.pc
|
|
);
|
|
let wst_idx = wst.borrow_mut().idx();
|
|
if wst_idx != 0 {
|
|
for i in 0..wst_idx {
|
|
print!(" #{:02X}", self.wst.borrow_mut().get1(i).unwrap());
|
|
}
|
|
} else {
|
|
print!(" empty")
|
|
}
|
|
print!("\n<return stack>");
|
|
let rst_idx = rst.borrow_mut().idx();
|
|
if rst_idx != 0 {
|
|
for i in 0..rst_idx {
|
|
print!(" #{:02X}", self.rst.borrow_mut().get1(i).unwrap());
|
|
}
|
|
} else {
|
|
print!(" empty")
|
|
}
|
|
println!();
|
|
}
|
|
|
|
pub fn debug_symbols(&self) {
|
|
for (k, v) in self.symbols.borrow().iter() {
|
|
println!(" #{:02X} {}", k, v);
|
|
}
|
|
}
|
|
|
|
fn device(&self, port: u8) -> Rc<RefCell<dyn Device>> {
|
|
let devnum = ((port & 0xF0) >> 4) as usize;
|
|
Rc::clone(&self.devices[devnum])
|
|
}
|
|
|
|
pub fn dei1(&mut self, port: u8) -> Result<u8, DeviceError> {
|
|
self.device(port).borrow_mut().dei1(self, port)
|
|
}
|
|
|
|
pub fn dei2(&mut self, port: u8) -> Result<u16, DeviceError> {
|
|
self.device(port).borrow_mut().dei2(self, port)
|
|
}
|
|
|
|
pub fn deo1(&mut self, port: u8, val: u8) -> Result<(), DeviceError> {
|
|
self.device(port).borrow_mut().deo1(self, port, val)
|
|
}
|
|
|
|
pub fn deo2(&mut self, port: u8, val: u16) -> Result<(), DeviceError> {
|
|
self.device(port).borrow_mut().deo2(self, port, val)
|
|
}
|
|
|
|
pub fn sta1(&mut self, address: u16, val: u8) -> Result<(), MemoryError> {
|
|
if self.is_tracing() {
|
|
eprintln!(
|
|
" STA #{:04X} ({}) <- #{:02X}",
|
|
address,
|
|
self.symbols.get(&address).unwrap_or(&"".to_string()),
|
|
val
|
|
)
|
|
}
|
|
self.memory.clone().borrow_mut().set1(address, val)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn sta2(&mut self, address: u16, val: u16) -> Result<(), MemoryError> {
|
|
if self.is_tracing() {
|
|
eprintln!(
|
|
" STA #{:04X} ({}) <- #{:04X}",
|
|
address,
|
|
self.symbols.get(&address).unwrap_or(&"".to_string()),
|
|
val
|
|
)
|
|
}
|
|
self.memory.clone().borrow_mut().set2(address, val)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn lda1(&self, address: u16) -> Result<u8, MemoryError> {
|
|
if address != self.pc && self.is_tracing() {
|
|
eprintln!(
|
|
" LDA #{:04X} ({})",
|
|
address,
|
|
self.symbols.get(&address).unwrap_or(&"".to_string()),
|
|
)
|
|
}
|
|
self.memory.clone().borrow_mut().get1(address)
|
|
}
|
|
|
|
pub fn lda2(&self, address: u16) -> Result<u16, MemoryError> {
|
|
if address != self.pc && self.is_tracing() {
|
|
eprintln!(
|
|
" LDA #{:04X} ({})",
|
|
address,
|
|
self.symbols.get(&address).unwrap_or(&"".to_string()),
|
|
)
|
|
}
|
|
self.memory.clone().borrow_mut().get2(address)
|
|
}
|
|
|
|
pub fn branch(&mut self, address: u16) {
|
|
match self.symbols.get(&address) {
|
|
Some(label) => {
|
|
if self.is_tracing() {
|
|
eprintln!(
|
|
"Branch #{:04X} to {} (#{:04X})",
|
|
self.pc - 1,
|
|
label,
|
|
address
|
|
)
|
|
}
|
|
}
|
|
None => (),
|
|
}
|
|
self.pc = address;
|
|
}
|
|
|
|
// Run one clock cycle (instruction)
|
|
pub fn step(&mut self) -> Result<(), UxnError> {
|
|
match self.lda1(self.pc) {
|
|
Err(e) => Err(UxnError::MemoryError(e)),
|
|
Ok(icode) => {
|
|
match self.symbols.get(&self.pc) {
|
|
Some(sym) => {
|
|
if self.is_tracing() {
|
|
eprintln!("{}:", sym)
|
|
}
|
|
}
|
|
None => (),
|
|
}
|
|
|
|
// The value of PC is defined to be the value of the NEXT pc ala Mips
|
|
self.pc += 1;
|
|
self.clock += 1;
|
|
|
|
// Short circuit for cheap NOPs (POPk, POPkr)
|
|
if (icode & !Icode::RETURN) == Icode::NOP {
|
|
return Ok(());
|
|
}
|
|
|
|
// Extract flags
|
|
let (kflag, rflag, sflag, icode5) = Icode::parse(icode);
|
|
|
|
if self.is_tracing() {
|
|
eprintln!(
|
|
" cycle #{:04X}; pc #{:04X}, rom pc: #{:04X}: {} ( {:05b} s: {:1x} r: {:1x} k: {:1x} )",
|
|
self.clock,
|
|
self.pc - 1,
|
|
self.pc - 0x0101,
|
|
Icode::nameof(icode),
|
|
icode5,
|
|
sflag,
|
|
rflag,
|
|
kflag
|
|
)
|
|
}
|
|
|
|
// Swizzle the stacks as needed
|
|
let [wst, rst]: [Rc<RefCell<dyn Stack>>; 2] = {
|
|
if rflag == 0 {
|
|
[self.wst.clone(), self.rst.clone()]
|
|
} else {
|
|
[self.rst.clone(), self.wst.clone()]
|
|
}
|
|
};
|
|
|
|
// Inject a PopStack shim to support the 'keep' bit as needed
|
|
let [wst, rst]: [Rc<RefCell<dyn Stack>>; 2] = {
|
|
if kflag == 0 {
|
|
[wst, rst]
|
|
} else {
|
|
[
|
|
Rc::new(RefCell::new(PopStack::new(wst))),
|
|
Rc::new(RefCell::new(PopStack::new(rst))),
|
|
]
|
|
}
|
|
};
|
|
|
|
// Some procedural abstractions over load/store sizes
|
|
let pop = |stack: Rc<RefCell<dyn Stack>>| -> Result<u16, StackError> {
|
|
if sflag == 1 {
|
|
stack.borrow_mut().pop2()
|
|
} else {
|
|
Ok(stack.borrow_mut().pop1()? as u16)
|
|
}
|
|
};
|
|
|
|
let push = |stack: Rc<RefCell<dyn Stack>>, val: u16| {
|
|
if sflag == 1 {
|
|
stack.borrow_mut().push2(val)
|
|
} else {
|
|
stack.borrow_mut().push1(val as u8)
|
|
}
|
|
};
|
|
|
|
let load = |addr: u16| -> Result<u16, MemoryError> {
|
|
if sflag == 1 {
|
|
self.lda2(addr)
|
|
} else {
|
|
Ok(self.lda1(addr)? as u16)
|
|
}
|
|
};
|
|
|
|
// FIXME: Can't use self.sta1/sta2 due to *self reference uniqueness
|
|
let store = |this: &mut Uxn, addr: u16, val: u16| {
|
|
if sflag == 1 {
|
|
this.sta2(addr, val)
|
|
} else {
|
|
this.sta1(addr, val as u8)
|
|
}
|
|
};
|
|
|
|
match (kflag, rflag, sflag, icode5) {
|
|
(0, 0, 0, Icode::BRK) => {
|
|
// BRK --
|
|
return Err(UxnError::Break);
|
|
}
|
|
(1, _, _, Icode::BRK) => {
|
|
// BRKk aka LIT -- a
|
|
let val = load(self.pc)?;
|
|
push(wst.clone(), val)?;
|
|
self.branch(self.pc + if sflag == 1 { 2 } else { 1 });
|
|
}
|
|
(_, _, _, Icode::INC) => {
|
|
// INC a -- a
|
|
push(wst.clone(), pop(wst.clone())?.wrapping_add(1))?;
|
|
}
|
|
(_, _, _, Icode::POP) => {
|
|
// POP a --
|
|
pop(wst)?;
|
|
}
|
|
(_, _, _, Icode::NIP) => {
|
|
// NIP a b -- b
|
|
let keep = pop(wst.clone())?;
|
|
pop(wst.clone())?;
|
|
push(wst.clone(), keep)?;
|
|
}
|
|
(_, _, _, Icode::SWP) => {
|
|
// SWP a b -- b a
|
|
let b = pop(wst.clone())?;
|
|
let a = pop(wst.clone())?;
|
|
push(wst.clone(), b)?;
|
|
push(wst.clone(), a)?;
|
|
}
|
|
(_, _, _, Icode::ROT) => {
|
|
// ROT a b c -- b c a
|
|
let c = pop(wst.clone())?;
|
|
let b = pop(wst.clone())?;
|
|
let a = pop(wst.clone())?;
|
|
push(wst.clone(), b)?;
|
|
push(wst.clone(), c)?;
|
|
push(wst.clone(), a)?;
|
|
}
|
|
(_, _, _, Icode::DUP) => {
|
|
// DUP a -- a a
|
|
let a = pop(wst.clone())?;
|
|
push(wst.clone(), a)?;
|
|
push(wst.clone(), a)?;
|
|
}
|
|
(_, _, _, Icode::OVR) => {
|
|
// OVR a b -- a b a
|
|
let b = pop(wst.clone())?;
|
|
let a = pop(wst.clone())?;
|
|
push(wst.clone(), a)?;
|
|
push(wst.clone(), b)?;
|
|
push(wst.clone(), a)?;
|
|
}
|
|
(_, _, _, Icode::EQL) => {
|
|
// EQU a b -- c
|
|
let b = pop(wst.clone())?;
|
|
let a = pop(wst.clone())?;
|
|
wst.borrow_mut().push1(if a == b { 1 } else { 0 })?;
|
|
}
|
|
(_, _, _, Icode::NEQ) => {
|
|
// NEQ a b -- c
|
|
let b = pop(wst.clone())?;
|
|
let a = pop(wst.clone())?;
|
|
wst.borrow_mut().push1(if a == b { 0 } else { 1 })?;
|
|
}
|
|
(_, _, _, Icode::GTH) => {
|
|
// GTH a b -- c
|
|
let b = pop(wst.clone())?;
|
|
let a = pop(wst.clone())?;
|
|
wst.borrow_mut().push1(if a > b { 1 } else { 0 })?;
|
|
}
|
|
(_, _, _, Icode::LTH) => {
|
|
// LTH a b -- c
|
|
let b = pop(wst.clone())?;
|
|
let a = pop(wst.clone())?;
|
|
wst.borrow_mut().push1(if a < b { 1 } else { 0 })?;
|
|
}
|
|
(_, _, 0, Icode::JMP) => {
|
|
// JMP1 a --
|
|
let delta = wst.borrow_mut().pop1()? as i8;
|
|
self.branch(self.pc.wrapping_add(delta as u16));
|
|
}
|
|
(_, _, 1, Icode::JMP) => {
|
|
// JMP2 a --
|
|
let target = wst.borrow_mut().pop2()?;
|
|
self.branch(target);
|
|
}
|
|
(_, _, 0, Icode::JCN) => {
|
|
// JCN1 cnd8 addr8 (relative)
|
|
let delta = wst.borrow_mut().pop1()? as i8;
|
|
let cnd = wst.borrow_mut().pop1()?;
|
|
if cnd != 0 {
|
|
self.branch(self.pc.wrapping_add(delta as u16));
|
|
}
|
|
}
|
|
(_, _, 1, Icode::JCN) => {
|
|
// JCN2 cnd8 addr16 (absolute)
|
|
let addr = wst.borrow_mut().pop2()?;
|
|
let cnd = wst.borrow_mut().pop1()?;
|
|
if cnd != 0 {
|
|
self.branch(addr);
|
|
}
|
|
}
|
|
(_, _, 0, Icode::JSR) => {
|
|
// JSR1 addr8 (relative)
|
|
rst.borrow_mut().push2(self.pc)?;
|
|
let delta = wst.borrow_mut().pop1()? as i8;
|
|
self.branch(self.pc.wrapping_add(delta as u16));
|
|
}
|
|
(_, _, 1, Icode::JSR) => {
|
|
// JSR2 addr16 (absolute)
|
|
rst.borrow_mut().push2(self.pc)?;
|
|
self.branch(wst.borrow_mut().pop2()?);
|
|
}
|
|
(_, _, _, Icode::STH) => {
|
|
// STH a
|
|
push(rst, pop(wst)?)?;
|
|
}
|
|
(_, _, _, Icode::LDZ) => {
|
|
// LDZ a8 -- b
|
|
let addr = wst.borrow_mut().pop1()? as u16;
|
|
push(wst.clone(), load(addr)?)?;
|
|
}
|
|
(_, _, _, Icode::STZ) => {
|
|
// STZ val addr8 --
|
|
let addr = wst.borrow_mut().pop1()? as u16;
|
|
store(self, addr, pop(wst.clone())?)?;
|
|
}
|
|
(_, _, _, Icode::LDR) => {
|
|
// LDR addr8 -- a8
|
|
let delta = wst.borrow_mut().pop1()? as i8;
|
|
let addr = self.pc.wrapping_add(delta as u16);
|
|
push(wst, load(addr)?)?;
|
|
}
|
|
(_, _, _, Icode::STR) => {
|
|
// STR val addr8 --
|
|
let delta = wst.borrow_mut().pop1()? as i8;
|
|
let addr = self.pc.wrapping_add(delta as u16);
|
|
store(self, addr, pop(wst)?)?;
|
|
}
|
|
(_, _, _, Icode::LDA) => {
|
|
// LDA a16
|
|
let addr = wst.borrow_mut().pop2()?;
|
|
push(wst.clone(), load(addr)?)?;
|
|
}
|
|
(_, _, _, Icode::STA) => {
|
|
// STA val a16 --
|
|
let addr = wst.borrow_mut().pop2()?;
|
|
store(self, addr, pop(wst.clone())?)?;
|
|
}
|
|
(_, _, 0, Icode::DEI) => {
|
|
// DEI port8 -- a8
|
|
let mut wst = wst.borrow_mut();
|
|
let port = wst.pop1()?;
|
|
wst.push1(self.dei1(port)?)?;
|
|
}
|
|
(_, _, 1, Icode::DEI) => {
|
|
// DEI2 port8 -- a16
|
|
let mut wst = wst.borrow_mut();
|
|
let port = wst.pop1()?;
|
|
wst.push2(self.dei2(port)?)?;
|
|
}
|
|
(_, _, 0, Icode::DEO) => {
|
|
// DEO1 a8 port8 --
|
|
let port = wst.borrow_mut().pop1()?;
|
|
let val = wst.borrow_mut().pop1()?;
|
|
self.deo1(port, val).unwrap();
|
|
}
|
|
(_, _, 1, Icode::DEO) => {
|
|
// DEO2 a16 port8 --
|
|
let mut wst = wst.borrow_mut();
|
|
let port = wst.pop1()?;
|
|
let val = wst.pop2()?;
|
|
self.deo2(port, val).unwrap();
|
|
}
|
|
(_, _, _, Icode::ADD) => {
|
|
// ADD a b -- c
|
|
let b = pop(wst.clone())?;
|
|
let a = pop(wst.clone())?;
|
|
push(wst.clone(), a.wrapping_add(b))?;
|
|
}
|
|
(_, _, _, Icode::SUB) => {
|
|
// SUB a b -- c
|
|
let b = pop(wst.clone())?;
|
|
let a = pop(wst.clone())?;
|
|
push(wst.clone(), a.wrapping_sub(b))?;
|
|
}
|
|
(_, _, _, Icode::MUL) => {
|
|
// MUL a b -- c
|
|
let b = pop(wst.clone())?;
|
|
let a = pop(wst.clone())?;
|
|
push(wst.clone(), a.wrapping_mul(b))?;
|
|
}
|
|
(_, _, _, Icode::DIV) => {
|
|
// DIV a b -- c
|
|
let b = pop(wst.clone())?;
|
|
let a = pop(wst.clone())?;
|
|
push(wst.clone(), a / b)?;
|
|
}
|
|
(_, _, _, Icode::AND) => {
|
|
// AND a b -- c
|
|
let b = pop(wst.clone())?;
|
|
let a = pop(wst.clone())?;
|
|
push(wst.clone(), a & b)?;
|
|
}
|
|
(_, _, _, Icode::ORA) => {
|
|
// OR a b -- c
|
|
let b = pop(wst.clone())?;
|
|
let a = pop(wst.clone())?;
|
|
push(wst.clone(), a | b)?;
|
|
}
|
|
(_, _, _, Icode::EOR) => {
|
|
// XOR a b -- c8
|
|
let b = pop(wst.clone())?;
|
|
let a = pop(wst.clone())?;
|
|
push(wst.clone(), a ^ b)?;
|
|
}
|
|
(_, _, _, Icode::SFT) => {
|
|
// SFT a shift8 -- b
|
|
let shift = wst.borrow_mut().pop1()?;
|
|
let [left, right] = [shift >> 4 & 0xF, shift & 0xF];
|
|
let a = pop(wst.clone())?;
|
|
push(wst.clone(), (a >> right) << left)?;
|
|
}
|
|
_ => {
|
|
panic!("Unsupported opcode {}", Icode::nameof(icode))
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn run(&mut self, limit: u16) -> Result<(), UxnError> {
|
|
let mut executed = 0;
|
|
loop {
|
|
if executed == limit {
|
|
return Err(UxnError::ExecutionLimit(executed));
|
|
}
|
|
executed += 1;
|
|
|
|
match self.step() {
|
|
Err(e) => return Err(e),
|
|
Ok(()) => continue,
|
|
};
|
|
}
|
|
}
|
|
}
|