[NO TESTS] WIP

This commit is contained in:
Reid 'arrdem' McKenzie 2022-12-19 00:29:30 -07:00
parent d7343b901f
commit b8eeecb2c3
6 changed files with 471 additions and 275 deletions

19
src/device.rs Normal file
View file

@ -0,0 +1,19 @@
pub mod console;
pub mod null;
pub mod system;
use crate::vm::Uxn;
pub trait Device: std::fmt::Debug {
/**
* Callbacks used by UXN to do input from a "device".
*/
fn dei1(&mut self, vm: &mut Uxn, port: u8) -> u8;
fn dei2(&mut self, vm: &mut Uxn, port: u8) -> u16;
/**
* Callback used by UXN to do output through a "device".
*/
fn deo1(&mut self, vm: &mut Uxn, port: u8, val: u8);
fn deo2(&mut self, vm: &mut Uxn, port: u8, val: u16);
}

57
src/device/console.rs Normal file
View file

@ -0,0 +1,57 @@
/**
* The system console device
*
* This device is ... woefully incomplete relative to the video device.
* It allows the UXN vm to poke characters into the standard out device.
* It has no standard specified facilities for standard error.
*
* When the user enters a key onto the emulator's console, the emulator is
* expected to enqueue and eventually deliver a signal to the device vector.
* As a facility to that end, the
*
*/
#[derive(Debug)]
struct ConsoleDevice {
vector: u16,
inbuffer: [u8; 256],
inidx: usize,
}
impl ConsoleDevice {
pub fn new() -> ConsoleDevice {
ConsoleDevice {
vector: 0,
inidx: 255,
inbuffer: [0; 256],
}
}
}
impl Device for ConsoleDevice {
/**
* DEI1 is tricky.
*
* If the input buffer is empty (inidx == inbuffer.size, default) we want to
* perform a buffered line read into inbuffer, and set inidx to 0.
*
* If inidx < inbuffer.size, we want to return the next character out of the
* inbuffer.
*/
fn dei1(&mut self, vm: &mut Uxn, port: u8) -> u8 {
let slot = port & 0xF;
match slot {
0x2 => 0,
_ => 0,
}
}
// DEI2 is not supported on the terminal device
fn dei2(&mut self, vm: &mut Uxn, port: u8) -> u16 {
0
}
/**
* Callback used by UXN to do output through a "device".
*/
fn deo1(&mut self, vm: &mut Uxn, port: u8, val: u8) {}
fn deo2(&mut self, vm: &mut Uxn, port: u8, val: u16) {}
}

22
src/device/null.rs Normal file
View file

@ -0,0 +1,22 @@
/**
* The null device does nothing when reading or writing.
*/
#[derive(Debug)]
pub struct NullDevice {}
impl NullDevice {
pub fn new() -> NullDevice {
NullDevice {}
}
}
impl Device for NullDevice {
fn dei1(&mut self, _vm: &mut Uxn, _port: u8) -> u8 {
0
}
fn dei2(&mut self, _vm: &mut Uxn, _port: u8) -> u16 {
0
}
fn deo1(&mut self, _vm: &mut Uxn, _port: u8, _val: u8) {}
fn deo2(&mut self, _vm: &mut Uxn, _port: u8, _val: u16) {}
}

50
src/device/system.rs Normal file
View file

@ -0,0 +1,50 @@
/**
* The system device
*/
#[derive(Debug)]
struct SystemDevice {
buffer: [u8; 0xF],
}
impl SystemDevice {
fn new() -> SystemDevice {
SystemDevice { buffer: [0; 0xF] }
}
}
impl Device for SystemDevice {
fn dei1(&mut self, vm: &mut Uxn, port: u8) -> u8 {
let slot = port & 0xF;
match slot {
0x2 => vm.wst.idx,
0x3 => vm.rst.idx,
x => self.buffer[x as usize],
}
}
fn dei2(&mut self, vm: &mut Uxn, port: u8) -> u16 {
return ((self.dei1(vm, port) as u16) << 8) + self.dei1(vm, port + 1) as u16;
}
fn deo1(&mut self, vm: &mut Uxn, port: u8, val: u8) {
let slot = port & 0xF;
match slot {
// Note: This VM does not support mutating the stack pointers.
// This operation is not well defined upstream
0x2 => panic!("Invoked unsupported mutation of the data stack pointer"),
0x3 => panic!("Invoked unsupported mutation of the return stack pointer"),
x => self.buffer[x as usize] = val,
}
}
fn deo2(&mut self, vm: &mut Uxn, port: u8, val: u16) {
let slot = port & 0xF;
assert!(slot < 0xF, "Double-write beyond the end of the device");
match slot {
0x2 => panic!("The data stack is single-width, panic on spurious double-write"),
0x3 => panic!("The return stack is single-width, panic on spurious double-write"),
x => {
let [high, low] = val.to_be_bytes();
self.deo1(vm, x, high);
self.deo1(vm, x + 1, low)
}
}
}
}

View file

@ -1,277 +1,4 @@
pub mod device;
pub mod memory;
pub mod stack;
use std::cell::{Cell, RefCell, RefMut};
use std::rc::Rc;
use std::*;
use memory::MemoryError;
use stack::StackError;
use crate::memory::{Memory, TrivialMemory};
use crate::stack::Stack;
trait Device: std::fmt::Debug {
/**
* Callbacks used by UXN to do input from a "device".
*/
fn dei1(&self, vm: &mut Uxn, port: u8) -> u8;
fn dei2(&self, vm: &mut Uxn, port: u8) -> u16;
/**
* Callback used by UXN to do output through a "device".
*/
fn deo1(&mut self, vm: &mut Uxn, port: u8, val: u8);
fn deo2(&mut self, vm: &mut Uxn, port: u8, val: u16);
}
/**
* The null device does nothing when reading or writing.
*/
#[derive(Debug)]
struct NullDevice {}
impl NullDevice {
pub fn new() -> NullDevice {
NullDevice {}
}
}
impl Device for NullDevice {
fn dei1(&self, vm: &mut Uxn, port: u8) -> u8 {
0
}
fn dei2(&self, vm: &mut Uxn, port: u8) -> u16 {
0
}
fn deo1(&mut self, vm: &mut Uxn, port: u8, val: u8) {}
fn deo2(&mut self, vm: &mut Uxn, port: u8, val: u16) {}
}
/**
* The system device
*/
#[derive(Debug)]
struct SystemDevice {
buffer: [u8; 0xF],
}
impl SystemDevice {
fn new() -> SystemDevice {
SystemDevice { buffer: [0; 0xF] }
}
}
impl Device for SystemDevice {
fn dei1(&self, vm: &mut Uxn, port: u8) -> u8 {
let slot = port & 0xF;
match slot {
0x2 => vm.wst.idx,
0x3 => vm.rst.idx,
x => self.buffer[x as usize],
}
}
fn dei2(&self, vm: &mut Uxn, port: u8) -> u16 {
return ((self.dei1(vm, port) as u16) << 8) + self.dei1(vm, port + 1) as u16;
}
fn deo1(&mut self, vm: &mut Uxn, port: u8, val: u8) {
let slot = port & 0xF;
match slot {
// Note: This VM does not support mutating the stack pointers.
// This operation is not well defined upstream
0x2 => panic!("Invoked unsupported mutation of the data stack pointer"),
0x3 => panic!("Invoked unsupported mutation of the return stack pointer"),
x => self.buffer[x as usize] = val,
}
}
fn deo2(&mut self, vm: &mut Uxn, port: u8, val: u16) {
let slot = port & 0xF;
assert!(slot < 0xF, "Double-write beyond the end of the device");
match slot {
0x2 => panic!("The data stack is single-width, panic on spurious double-write"),
0x3 => panic!("The return stack is single-width, panic on spurious double-write"),
x => {
let [high, low] = val.to_be_bytes();
self.deo1(vm, x, high);
self.deo1(vm, x + 1, low)
}
}
}
}
pub enum UxnError {
StackError(StackError),
MemoryError(MemoryError),
ExecutionLimit(u16),
ArithmeticOverflow,
ArithmeticUnderflow,
DivisionByZero,
}
#[derive(Debug)]
pub struct Uxn {
memory: Rc<RefCell<TrivialMemory>>,
// Note: Using Rc so we can start with many NullDevs and replace them
devices: [Rc<RefCell<dyn Device>>; 16],
pc: u16, // Program counter
wst: Stack, // Data stack
rst: Stack, // Return stack pointer
}
impl Uxn {
pub fn new() -> Uxn {
Uxn {
memory: Rc::new(RefCell::new(TrivialMemory::new())),
devices: [
Rc::new(RefCell::new(SystemDevice::new())), // #00
Rc::new(RefCell::new(NullDevice::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,
wst: Stack::new(),
rst: Stack::new(),
}
}
pub fn is_halted(&mut self) -> bool {
self.dei1(0x0f) != 0
}
fn device(&self, port: u8) -> Rc<RefCell<dyn Device>> {
Rc::clone(&self.devices[(port & 0xF0 >> 4) as usize])
}
pub fn dei1(&mut self, port: u8) -> u8 {
self.device(port).borrow_mut().dei1(self, port)
}
pub fn dei2(&mut self, port: u8) -> u16 {
self.device(port).borrow_mut().dei2(self, port)
}
pub fn deo1(&mut self, port: u8, val: u8) {
self.device(port).borrow_mut().deo1(self, port, val);
}
pub fn deo2(&mut self, port: u8, val: u16) {
self.device(port).borrow_mut().deo2(self, port, val);
}
pub fn sta1(&mut self, address: u16, val: u8) -> Result<(), MemoryError> {
self.memory.borrow_mut().set1(address, val)?;
Ok(())
}
pub fn sta2(&mut self, address: u16, val: u16) -> Result<(), MemoryError> {
self.memory.borrow_mut().set2(address, val)?;
Ok(())
}
pub fn lda1(&mut self, address: u16) -> Result<u8, MemoryError> {
self.memory.borrow().get1(address)
}
// Run one clock cycle (instruction)
pub fn step(&mut self) -> Result<(), UxnError> {
Ok(())
}
pub fn run(&mut self, limit: u16) -> Result<(), UxnError> {
let mut executed = 0;
loop {
if executed == limit {
return Err(UxnError::ExecutionLimit(executed));
}
if self.is_halted() {
break;
}
match self.lda1(self.pc) {
Err(e) => return Err(UxnError::MemoryError(e)),
Ok(0) => break,
Ok(opcode) => {
// Extract flags
let sflag: bool = opcode & 0x20 != 0;
let rflag: bool = opcode & 0x40 != 0;
let kflag: bool = opcode & 0x80 != 0;
match opcode & 0x1F {
0x00 => {
// LIT1
()
}
0x01 => {
// INC1
()
}
0x02 => {
// POP1
()
}
0x03 => {
// NIP1
()
}
0x04 => {
// SWP1
()
}
0x05 => {
// ROT1
()
}
0x06 => {
// DUP1
()
}
0x07 => {
// OVR1
}
0x08 => {
// EQ1
()
}
0x09 => {
// NEQ1
()
}
0x0a => {
// GTH1
()
}
0x0b => {
// LTH1
()
}
0x0c => {
// JMP1
}
0x0d => {
// JCN1
}
0x0e => {
// JSR1
}
_ => (),
}
}
}
executed += 1;
}
Ok(())
}
}
pub mod vm;

321
src/vm.rs Normal file
View file

@ -0,0 +1,321 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::*;
use crate::device::*;
use crate::memory::*;
use crate::stack::*;
pub enum UxnError {
StackError(StackError),
MemoryError(MemoryError),
ExecutionLimit(u16),
ArithmeticOverflow,
ArithmeticUnderflow,
DivisionByZero,
}
#[derive(Debug)]
pub struct Uxn {
memory: Rc<RefCell<TrivialMemory>>,
// Note: Using Rc so we can start with many NullDevs and replace them
devices: [Rc<RefCell<dyn Device>>; 16],
pc: u16, // Program counter
wst: Stack, // Data stack
rst: Stack, // Return stack pointer
}
impl Uxn {
pub fn new() -> Uxn {
Uxn {
memory: Rc::new(RefCell::new(TrivialMemory::new())),
devices: [
Rc::new(RefCell::new(SystemDevice::new())), // #00
Rc::new(RefCell::new(NullDevice::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,
wst: Stack::new(),
rst: Stack::new(),
}
}
pub fn is_halted(&mut self) -> bool {
self.dei1(0x0f) != 0
}
fn device(&self, port: u8) -> Rc<RefCell<dyn Device>> {
Rc::clone(&self.devices[(port & 0xF0 >> 4) as usize])
}
pub fn dei1(&mut self, port: u8) -> u8 {
self.device(port).borrow_mut().dei1(self, port)
}
pub fn dei2(&mut self, port: u8) -> u16 {
self.device(port).borrow_mut().dei2(self, port)
}
pub fn deo1(&mut self, port: u8, val: u8) {
self.device(port).borrow_mut().deo1(self, port, val);
}
pub fn deo2(&mut self, port: u8, val: u16) {
self.device(port).borrow_mut().deo2(self, port, val);
}
pub fn sta1(&mut self, address: u16, val: u8) -> Result<(), MemoryError> {
self.memory.borrow_mut().set1(address, val)?;
Ok(())
}
pub fn sta2(&mut self, address: u16, val: u16) -> Result<(), MemoryError> {
self.memory.borrow_mut().set2(address, val)?;
Ok(())
}
pub fn lda1(&mut self, address: u16) -> Result<u8, MemoryError> {
self.memory.borrow().get1(address)
}
// Run one clock cycle (instruction)
pub fn step(&mut self) -> Result<(), UxnError> {
Ok(())
}
pub fn run(&mut self, limit: u16) -> Result<(), UxnError> {
let mut executed = 0;
'run: loop {
if executed == limit {
return Err(UxnError::ExecutionLimit(executed));
}
if self.is_halted() {
break;
}
executed += 1;
match self.lda1(self.pc) {
Err(e) => return Err(UxnError::MemoryError(e)),
Ok(opcode) => {
// Extract flags
let sflag: bool = opcode & 0x20 != 0;
let rflag: bool = opcode & 0x40 != 0;
let kflag: bool = opcode & 0x80 != 0;
let icode: u8 = opcode & 0x1F;
let wst = || if rflag { self.wst } else { self.rst };
let mut wd = 0u8; // Accumulated pop
let mut widx = || {
assert!(wd <= wst().idx);
wst().idx - wd
}; // Current index
let rst = || if rflag { self.rst } else { self.wst };
let mut rd = 0u8; // Accumulated pop
let mut ridx = || rst().idx - rd; // Current index
match (kflag, rflag, sflag, icode) {
(0, 0, 0, 0x00) => {
// BRK
break 'run;
}
(_, _, 0, 0x00) => {
// LIT1
wst().push1(self.ld1(self.pc + 1));
self.pc += 1;
continue 'run;
}
(_, _, 1, 0x00) => {
// LIT2
wst().push2(self.ld2(self.pc + 1));
self.pc += 2;
continue 'run;
}
(_, _, 0, 0x01) => {
// INC1
let a = wst().pop1();
wst().push1(a + 1);
}
(_, _, 1, 0x01) => {
// INC1
let a = wst().pop2();
wst().push2(a + 1);
}
(_, _, 0, 0x02) => {
// POP1
wst().pop1();
}
(_, _, 1, 0x02) => {
// POP2
wst().pop2();
}
(_, _, 0, 0x03) => {
// NIP1 a b -- a
let keep = wst().pop1();
wst().pop1();
wst().push1(keep);
}
(_, _, 1, 0x03) => {
// NIP2 a b -- a
let keep = wst().pop12();
wst().pop2();
wst().push2(keep);
}
(_, _, 0, 0x04) => {
// SWP1
let a = wst().pop1();
let b = wst().pop1();
wst().push1(a);
wst().push1(b);
}
(_, _, 1, 0x04) => {
// SWP2
let a = wst().pop2();
let b = wst().pop2();
wst().push2(a);
wst().push2(b);
}
(_, _, 0, 0x05) => {
// ROT
let a = wst().pop1();
let b = wst().pop1();
let c = wst().pop1();
wst().push1(b);
wst().push1(c);
wst().push1(a);
}
(_, _, 1, 0x05) => {
// ROT
let a = wst().pop2();
let b = wst().pop2();
let c = wst().pop2();
wst().push2(b);
wst().push2(c);
wst().push2(a);
}
(_, _, _, 0x06) => {
// DUP
()
}
(_, _, _, 0x07) => {
// OVR
()
}
(_, _, _, 0x08) => {
// EQ
()
}
(_, _, _, 0x09) => {
// NEQ
()
}
(_, _, _, 0x0a) => {
// GTH
()
}
(_, _, _, 0x0b) => {
// LTH
()
}
(_, _, _, 0x0c) => {
// JMP
()
}
(_, _, _, 0x0d) => {
// JCN
()
}
(_, _, _, 0x0e) => {
// JSR
()
}
(_, _, _, 0x0f) => {
// STH
()
}
(_, _, _, 0x10) => {
// LDZ
()
}
(_, _, _, 0x11) => {
// STZ
()
}
(_, _, _, 0x12) => {
// LDR
()
}
(_, _, _, 0x13) => {
// STR
()
}
(_, _, _, 0x14) => {
// LDA
()
}
(_, _, _, 0x15) => {
// STA
()
}
(_, _, _, 0x16) => {
// DEI
()
}
(_, _, _, 0x17) => {
// DEO
()
}
(_, _, _, 0x18) => {
// ADD
()
}
(_, _, _, 0x19) => {
// SUB
()
}
(_, _, _, 0x1a) => {
// MUL
()
}
(_, _, _, 0x1b) => {
// DIV
()
}
(_, _, _, 0x1c) => {
// AND
()
}
(_, _, _, 0x1d) => {
// OR
()
}
(_, _, _, 0x1e) => {
// XOR
()
}
(_, _, _, 0x1f) => {
// SFT
()
}
_ => unreachable!(),
}
}
}
}
Ok(())
}
}