Testing & bugstomping

This commit is contained in:
Reid 'arrdem' McKenzie 2022-12-25 21:49:05 -07:00
parent 8ef06a7e1a
commit 93476015c5
13 changed files with 655 additions and 164 deletions

330
Cargo.lock generated
View file

@ -2,6 +2,164 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "env_logger"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
dependencies = [
"log",
"regex",
]
[[package]]
name = "futures"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
[[package]]
name = "futures-executor"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
[[package]]
name = "futures-macro"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
[[package]]
name = "futures-task"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
[[package]]
name = "futures-timer"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
[[package]]
name = "futures-util"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]] [[package]]
name = "nofmt" name = "nofmt"
version = "1.0.0" version = "1.0.0"
@ -9,42 +167,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06910a54adb901a01dfd9c475e7959c41acd55a078101ec70fb180f01b7435f" checksum = "b06910a54adb901a01dfd9c475e7959c41acd55a078101ec70fb180f01b7435f"
[[package]] [[package]]
name = "num_enum" name = "pin-project-lite"
version = "0.5.7" version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
dependencies = [
"num_enum_derive",
]
[[package]] [[package]]
name = "num_enum_derive" name = "pin-utils"
version = "0.5.7" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "once_cell"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
[[package]]
name = "proc-macro-crate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
dependencies = [
"once_cell",
"thiserror",
"toml",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
@ -55,6 +187,17 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "quickcheck"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
dependencies = [
"env_logger",
"log",
"rand",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.23" version = "1.0.23"
@ -65,10 +208,89 @@ dependencies = [
] ]
[[package]] [[package]]
name = "serde" name = "rand"
version = "1.0.151" version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "regex"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rstest"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b07f2d176c472198ec1e6551dc7da28f1c089652f66a7b722676c2238ebc0edf"
dependencies = [
"futures",
"futures-timer",
"rstest_macros",
"rustc_version",
]
[[package]]
name = "rstest_macros"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7229b505ae0706e64f37ffc54a9c163e11022a6636d58fe1f3f52018257ff9f7"
dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"rustc_version",
"syn",
"unicode-ident",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "semver"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
[[package]]
name = "slab"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "syn" name = "syn"
@ -81,35 +303,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "thiserror"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "toml"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.6" version = "1.0.6"
@ -121,5 +314,12 @@ name = "uxn"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"nofmt", "nofmt",
"num_enum", "quickcheck",
"rstest",
] ]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

View file

@ -3,8 +3,17 @@ name = "uxn"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[[bin]]
name = "uxncli"
path = "src/bin/uxncli/main.rs"
[[bin]]
name = "uxnsym"
path = "src/bin/uxnsym/main.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dev-dependencies]
quickcheck = "1.0.3"
rstest = "0.16.0"
nofmt = "1.0" nofmt = "1.0"
num_enum = "0.5.7"

View file

@ -1,24 +0,0 @@
use std::env::args;
use std::fs::File;
use std::io::BufReader;
use std::io::Read;
use uxn::vm::Uxn;
fn main() -> Result<(), std::io::Error> {
let mut vm = Uxn::new();
let argv: Vec<String> = args().collect();
let f: File = File::open(&argv[1])?;
let reader = BufReader::new(f);
let mut i = 0x0100u16;
for b in reader.bytes() {
vm.sta1(i, b?).expect("Write failed");
i += 1;
}
println!("{:?}", vm);
Ok(())
}

71
src/bin/uxncli/main.rs Normal file
View file

@ -0,0 +1,71 @@
use std::env::args;
use std::ffi::CStr;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Read;
use uxn::vm::{Uxn, UxnError};
fn main() -> Result<(), std::io::Error> {
let mut vm = Uxn::new();
let argv: Vec<String> = args().collect();
// Load a bytecode file
let f: File = File::open(&argv[1])?;
let reader = BufReader::new(f);
let mut i = 0x0100u16;
for b in reader.bytes() {
vm.sta1(i, b?).expect("Write failed");
i += 1;
}
// Load a symbol table
if argv.len() > 2 {
let f: File = File::open(&argv[2])?;
let mut reader: BufReader<File> = BufReader::new(f);
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();
eprintln!("#{:04X} {}", addr, label);
vm.symbols.insert(addr, label);
}
}
// Execute the VM
loop {
if vm.is_halted() {
return Ok(());
}
match vm.run(1024) {
Ok(()) => return Ok(()),
Err(UxnError::Break) => {
eprintln!("Interpreter broke; continuing...");
continue;
}
Err(UxnError::ExecutionLimit(_)) => continue,
Err(e) => {
panic!("Got unexpected interpretation error {:?}", e)
}
}
}
}

38
src/bin/uxnsym/main.rs Normal file
View file

@ -0,0 +1,38 @@
use std::env::args;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Read;
use uxn::vm::{Uxn, UxnError};
fn main() -> Result<(), std::io::Error> {
let mut vm = Uxn::new();
let argv: Vec<String> = args().collect();
let f: File = File::open(&argv[1])?;
let mut reader = BufReader::new(f);
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)?;
vm.symbols.insert(
addr,
std::str::from_utf8(sym_buff.as_slice())
.unwrap()
.to_string(),
);
}
vm.debug_symbols();
Ok(())
}

View file

@ -18,11 +18,18 @@ impl BuffDevice {
impl Device for BuffDevice { impl Device for BuffDevice {
fn dei1(&mut self, _vm: &mut Uxn, port: u8) -> Result<u8, DeviceError> { fn dei1(&mut self, _vm: &mut Uxn, port: u8) -> Result<u8, DeviceError> {
let slot = (port & 0xF) as usize; let slot = (port & 0x0F) as usize;
let port = port & 0xF0;
println!("Got read from device {:02X} slot {:02X}", port, slot);
Ok(self.buffer[slot]) Ok(self.buffer[slot])
} }
fn deo1(&mut self, _vm: &mut Uxn, port: u8, val: u8) -> Result<(), DeviceError> { fn deo1(&mut self, _vm: &mut Uxn, port: u8, val: u8) -> Result<(), DeviceError> {
let slot = (port & 0xF) as usize; let slot = (port & 0x0F) as usize;
let port = port & 0xF0;
println!(
"Got write to device {:02X} slot {:02X} val {:02X}",
port, slot, val
);
self.buffer[slot as usize] = val; self.buffer[slot as usize] = val;
Ok(()) Ok(())
} }

View file

@ -1,3 +1,5 @@
use std::io::{stderr, stdout, Write};
use crate::device::{Device, DeviceError}; use crate::device::{Device, DeviceError};
use crate::vm::Uxn; use crate::vm::Uxn;
@ -59,9 +61,32 @@ impl Device for ConsoleDevice {
/** /**
* Callback used by UXN to do output through a "device". * Callback used by UXN to do output through a "device".
*/ */
fn deo1(&mut self, vm: &mut Uxn, port: u8, val: u8) -> Result<(), DeviceError> { fn deo1(&mut self, _vm: &mut Uxn, port: u8, val: u8) -> Result<(), DeviceError> {
let slot = port & 0xF;
match slot {
0x08 => {
stdout().write_all(&[val]).unwrap();
if val == 0x0A {
stdout().flush().unwrap();
}
Ok(()) Ok(())
} }
0x09 => {
stderr().write_all(&[val]).unwrap();
if val == 0x0A {
stderr().flush().unwrap();
}
Ok(())
}
_ => {
eprintln!(
"Got write #{:02X} to unexpected slot #{:02X} (port #{:02X})",
val, slot, port
);
Ok(())
}
}
}
fn deo2(&mut self, vm: &mut Uxn, port: u8, val: u16) -> Result<(), DeviceError> { fn deo2(&mut self, vm: &mut Uxn, port: u8, val: u16) -> Result<(), DeviceError> {
Err(DeviceError::PortTypeError( Err(DeviceError::PortTypeError(

View file

@ -1,4 +1,9 @@
use std::borrow::Borrow;
use std::cell::RefCell;
use std::rc::Rc;
use crate::device::{Device, DeviceError}; use crate::device::{Device, DeviceError};
use crate::stack::Stack;
use crate::vm::Uxn; use crate::vm::Uxn;
/** /**
@ -35,8 +40,16 @@ impl Device for SystemDevice {
0x0 => (self.vector >> 8) as u8, 0x0 => (self.vector >> 8) as u8,
0x1 => (self.vector & 0xFF) as u8, 0x1 => (self.vector & 0xFF) as u8,
// The stack ports (somewhat nonstandard) // The stack ports (somewhat nonstandard)
0x2 => vm.wst.borrow().idx(), 0x2 => {
0x3 => vm.rst.borrow().idx(), let wst: Rc<RefCell<dyn Stack>> = vm.wst.clone();
let val = wst.borrow_mut().idx();
val
}
0x3 => {
let rst: Rc<RefCell<dyn Stack>> = vm.rst.clone();
let val = rst.borrow_mut().idx();
val
}
// Red // Red
0x8 => (self.red >> 8) as u8, 0x8 => (self.red >> 8) as u8,
@ -52,7 +65,7 @@ impl Device for SystemDevice {
// The state ports // The state ports
0xe => self.debug, 0xe => self.debug,
0xf => self.state, 0xf => 0,
_ => unreachable!(), _ => unreachable!(),
}) })
} }
@ -108,7 +121,9 @@ impl Device for SystemDevice {
} }
// The state ports // The state ports
0xe => self.debug = val, 0xe => {
vm.debug();
}
0xf => self.state = val, 0xf => self.state = val,
_ => unreachable!(), _ => unreachable!(),
} }

View file

@ -4,7 +4,8 @@ pub struct Icode {}
impl Icode { impl Icode {
// Official names // Official names
pub const BRK: u8 = 0x00u8; pub const BRK: u8 = 0x00u8;
pub const LIT: u8 = 0x1 << 7; // AKA keep Yes really. Per spec; pub const LIT: u8 = 0b10000000; // AKA keep Yes really. Per spec;
pub const LIT2: u8 = 0b10100000; // AKA keep Yes really. Per spec;
pub const INC: u8 = 0x01; pub const INC: u8 = 0x01;
pub const POP: u8 = 0x02; pub const POP: u8 = 0x02;
pub const NIP: u8 = 0x03; pub const NIP: u8 = 0x03;
@ -38,9 +39,9 @@ impl Icode {
pub const SFT: u8 = 0x1f; pub const SFT: u8 = 0x1f;
// Opcode flags // Opcode flags
pub const SHORT: u8 = 0x1 << 5; pub const SHORT: u8 = 0x20;
pub const RETURN: u8 = 0x1 << 6; pub const RETURN: u8 = 0x40;
pub const KEEP: u8 = 0x1 << 7; pub const KEEP: u8 = 0x80;
// Hacking around the official names for some things // Hacking around the official names for some things
pub const XOR: u8 = Icode::EOR; pub const XOR: u8 = Icode::EOR;
@ -58,4 +59,46 @@ impl Icode {
let icode: u8 = opcode & 0x1F; let icode: u8 = opcode & 0x1F;
(kflag, rflag, sflag, icode) (kflag, rflag, sflag, icode)
} }
pub const fn nameof(opcode: u8) -> &'static str {
match opcode {
// Nonstandard icodes + extensions
Icode::LIT | Icode::LIT2 => &"LIT", // AKA keep Yes really. Per spec,
Icode::NOP => &"NOP",
// Standard icodes
Icode::BRK => &"BRK",
Icode::INC => &"INC",
Icode::POP => &"POP",
Icode::NIP => &"NIP",
Icode::SWP => &"SWP",
Icode::ROT => &"ROT",
Icode::DUP => &"DUP",
Icode::OVR => &"OVR",
Icode::EQL => &"EQL",
Icode::NEQ => &"NEQ",
Icode::GTH => &"GTH",
Icode::LTH => &"LTH",
Icode::JMP => &"JMP",
Icode::JCN => &"JNC",
Icode::JSR => &"JSR",
Icode::STH => &"STH",
Icode::LDZ => &"LDZ",
Icode::STZ => &"STZ",
Icode::LDR => &"LDR",
Icode::STR => &"STR",
Icode::LDA => &"LDA",
Icode::STA => &"STA",
Icode::DEI => &"DEI",
Icode::DEO => &"DEO",
Icode::ADD => &"ADD",
Icode::SUB => &"SUB",
Icode::MUL => &"MUL",
Icode::DIV => &"DIV",
Icode::AND => &"AND",
Icode::ORA => &"ORA",
Icode::EOR => &"EOR",
Icode::SFT => &"SFT",
_ => Icode::nameof(opcode & 0x1F),
}
}
} }

View file

@ -3,3 +3,7 @@ pub mod isa;
pub mod memory; pub mod memory;
pub mod stack; pub mod stack;
pub mod vm; pub mod vm;
#[cfg(test)]
#[macro_use]
extern crate quickcheck;

View file

@ -91,6 +91,8 @@ impl Stack for ArrayStack {
#[cfg(test)] #[cfg(test)]
mod arrs_test { mod arrs_test {
use super::*; use super::*;
use quickcheck::*;
use rstest::*;
#[test] #[test]
fn test_push_pop() { fn test_push_pop() {
@ -167,4 +169,41 @@ mod arrs_test {
Err(_) => assert!(false, "poppoing stack of size 0 didn't underflow"), Err(_) => assert!(false, "poppoing stack of size 0 didn't underflow"),
} }
} }
quickcheck! {
fn set1_get1_round_trip_quick(val: u8) -> bool {
let mut s = ArrayStack::new();
s.push1(val).unwrap();
s.pop1() == Ok(val)
}
}
#[rstest]
#[case(0x00)]
#[case(0x80)]
#[case(0xFF)]
fn set1_get1_round_trip_manual(#[case] val: u8) {
let mut s = ArrayStack::new();
s.push1(val).unwrap();
assert_eq!(s.pop1(), Ok(val))
}
quickcheck! {
fn set2_get2_round_trip_quick(val: u16) -> bool {
let mut s = ArrayStack::new();
s.push2(val).unwrap();
s.pop2() == Ok(val)
}
}
#[rstest]
#[case(0x00)]
#[case(0x8000)]
#[case(0x0080)]
#[case(0xFFFF)]
fn set2_get2_round_trip_manual(#[case] val: u16) {
let mut s = ArrayStack::new();
s.push2(val).unwrap();
assert_eq!(s.pop2(), Ok(val))
}
} }

137
src/vm.rs
View file

@ -1,7 +1,10 @@
use std::borrow::Borrow;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use std::*; use std::*;
use crate::device::console::ConsoleDevice;
use crate::device::null::NullDevice; use crate::device::null::NullDevice;
use crate::device::system::SystemDevice; use crate::device::system::SystemDevice;
use crate::device::*; use crate::device::*;
@ -47,17 +50,19 @@ pub struct Uxn {
// Note: Using Rc so we can start with many NullDevs and replace them // Note: Using Rc so we can start with many NullDevs and replace them
pub devices: [Rc<RefCell<dyn Device>>; 16], pub devices: [Rc<RefCell<dyn Device>>; 16],
pub pc: u16, // Program counter pub pc: u16, // Program counter
pub clock: u64, // Executed instructions count
pub wst: Rc<RefCell<dyn Stack>>, // Data stack pub wst: Rc<RefCell<dyn Stack>>, // Data stack
pub rst: Rc<RefCell<dyn Stack>>, // Return stack pointer pub rst: Rc<RefCell<dyn Stack>>, // Return stack pointer
pub symbols: HashMap<u16, String>, // Symbol table
} }
impl Uxn { impl Uxn {
pub fn new() -> Uxn { pub fn new() -> Uxn {
Uxn { let mut uxn = Uxn {
memory: Rc::new(RefCell::new(TrivialMemory::new())), memory: Rc::new(RefCell::new(TrivialMemory::new())),
devices: [ devices: [
Rc::new(RefCell::new(SystemDevice::new())), // #00 Rc::new(RefCell::new(SystemDevice::new())), // #00
Rc::new(RefCell::new(NullDevice::new())), // #01 Rc::new(RefCell::new(ConsoleDevice::new())), // #01
Rc::new(RefCell::new(NullDevice::new())), // #02 Rc::new(RefCell::new(NullDevice::new())), // #02
Rc::new(RefCell::new(NullDevice::new())), // #03 Rc::new(RefCell::new(NullDevice::new())), // #03
Rc::new(RefCell::new(NullDevice::new())), // #04 Rc::new(RefCell::new(NullDevice::new())), // #04
@ -74,9 +79,13 @@ impl Uxn {
Rc::new(RefCell::new(NullDevice::new())), // #0f Rc::new(RefCell::new(NullDevice::new())), // #0f
], ],
pc: 0x0100, pc: 0x0100,
clock: 0,
wst: Rc::new(RefCell::new(ArrayStack::new())), wst: Rc::new(RefCell::new(ArrayStack::new())),
rst: 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 { pub fn of1(program: &[u8]) -> Uxn {
@ -93,8 +102,35 @@ impl Uxn {
self.dei1(0x0f).unwrap() != 0 self.dei1(0x0f).unwrap() != 0
} }
pub fn debug(&mut self) {
let wst = self.wst.clone();
let rst = self.rst.clone();
eprint!(
"<clock> #{:04X}\n<pc> #{:04X}\n<data stack>",
self.clock, self.pc
);
let wst_idx = wst.borrow_mut().idx();
for i in 0..wst_idx {
eprint!(" #{:02X}", self.wst.borrow_mut().get1(i).unwrap());
}
eprint!("\n<return stack>");
let rst_idx = rst.borrow_mut().idx();
for i in 0..rst_idx {
eprint!(" #{:02X}", self.rst.borrow_mut().get1(i).unwrap());
}
eprint!("\n");
}
pub fn debug_symbols(&self) {
for (k, v) in self.symbols.borrow().into_iter() {
println!(" #{:02X} {}", k, v);
}
}
fn device(&self, port: u8) -> Rc<RefCell<dyn Device>> { fn device(&self, port: u8) -> Rc<RefCell<dyn Device>> {
Rc::clone(&self.devices[(port & 0xF0 >> 4) as usize]) let devnum = ((port & 0xF0) >> 4) as usize;
Rc::clone(&self.devices[devnum])
} }
pub fn dei1(&mut self, port: u8) -> Result<u8, DeviceError> { pub fn dei1(&mut self, port: u8) -> Result<u8, DeviceError> {
@ -124,11 +160,24 @@ impl Uxn {
} }
pub fn lda1(&mut self, address: u16) -> Result<u8, MemoryError> { pub fn lda1(&mut self, address: u16) -> Result<u8, MemoryError> {
self.memory.borrow().get1(address) self.memory.clone().borrow_mut().get1(address)
} }
pub fn lda2(&mut self, address: u16) -> Result<u16, MemoryError> { pub fn lda2(&mut self, address: u16) -> Result<u16, MemoryError> {
self.memory.borrow().get2(address) self.memory.clone().borrow_mut().get2(address)
}
pub fn branch(&mut self, address: u16) {
match self.symbols.get(&address) {
Some(label) => eprintln!(
"Branch #{:04X} to {} (#{:04X})",
self.pc - 1,
label,
address
),
None => (),
}
self.pc = address;
} }
// Run one clock cycle (instruction) // Run one clock cycle (instruction)
@ -136,20 +185,33 @@ impl Uxn {
match self.lda1(self.pc) { match self.lda1(self.pc) {
Err(e) => Err(UxnError::MemoryError(e)), Err(e) => Err(UxnError::MemoryError(e)),
Ok(icode) => { Ok(icode) => {
match self.symbols.get(&self.pc) {
Some(sym) => eprintln!("{}:", sym),
None => (),
}
// The value of PC is defined to be the value of the NEXT pc ala Mips // The value of PC is defined to be the value of the NEXT pc ala Mips
self.pc += 1; self.pc += 1;
self.clock += 1;
// Short circuit for cheap NOPs (POPk, POPkr) // Short circuit for cheap NOPs (POPk, POPkr)
if icode & Icode::NOP == Icode::NOP { if (icode & !Icode::RETURN) == Icode::NOP {
return Ok(()); return Ok(());
} }
// Extract flags // Extract flags
let (kflag, rflag, sflag, icode) = Icode::parse(icode); let (kflag, rflag, sflag, icode5) = Icode::parse(icode);
println!( eprintln!(
"pc #{:04X}: #{:02x} ( {:05b} s: {:1x} r: {:1x} k: {:1x} )", " cycle #{:04X}; pc #{:04X}, rom pc: #{:04X}: {} ( {:05b} s: {:1x} r: {:1x} k: {:1x} )",
self.pc, icode, icode, sflag, rflag, kflag self.clock,
self.pc - 1,
self.pc - 0x0101,
Icode::nameof(icode),
icode5,
sflag,
rflag,
kflag
); );
// Swizzle the stacks as needed // Swizzle the stacks as needed
@ -206,19 +268,25 @@ impl Uxn {
} }
}; };
match (kflag, rflag, sflag, icode) { match (kflag, rflag, sflag, icode5) {
(0, 0, 0, Icode::BRK) => { (0, 0, 0, Icode::BRK) => {
// BRK -- // BRK --
return Err(UxnError::Break); return Err(UxnError::Break);
} }
(1, _, _, Icode::BRK) => { (1, _, _, Icode::BRK) => {
// BRKk aka LIT -- a // BRKk aka LIT -- a
push(wst.clone(), load(self.pc)?)?; let val = load(self.pc)?;
self.pc += if sflag == 1 { 2 } else { 1 }; if sflag == 0 {
eprintln!(" LIT #{:02X}", val);
} else {
eprintln!(" LIT #{:04X}", val);
}
push(wst.clone(), val)?;
self.branch(self.pc + if sflag == 1 { 2 } else { 1 });
} }
(_, _, _, Icode::INC) => { (_, _, _, Icode::INC) => {
// INC a -- a // INC a -- a
push(wst.clone(), pop(wst.clone())? + 1)?; push(wst.clone(), pop(wst.clone())?.wrapping_add(1))?;
} }
(_, _, _, Icode::POP) => { (_, _, _, Icode::POP) => {
// POP a -- // POP a --
@ -287,19 +355,19 @@ impl Uxn {
(_, _, 0, Icode::JMP) => { (_, _, 0, Icode::JMP) => {
// JMP1 a -- // JMP1 a --
let delta = wst.borrow_mut().pop1()? as i8; let delta = wst.borrow_mut().pop1()? as i8;
self.pc = self.pc.wrapping_add(delta as u16); self.branch(self.pc.wrapping_add(delta as u16));
} }
(_, _, 1, Icode::JMP) => { (_, _, 1, Icode::JMP) => {
// JMP2 a -- // JMP2 a --
let delta = wst.borrow_mut().pop2()?; let target = wst.borrow_mut().pop2()?;
self.pc = delta; self.branch(target);
} }
(_, _, 0, Icode::JCN) => { (_, _, 0, Icode::JCN) => {
// JCN1 cnd8 addr8 (relative) // JCN1 cnd8 addr8 (relative)
let delta = wst.borrow_mut().pop1()? as i8; let delta = wst.borrow_mut().pop1()? as i8;
let cnd = wst.borrow_mut().pop1()?; let cnd = wst.borrow_mut().pop1()?;
if cnd != 0 { if cnd != 0 {
self.pc = self.pc.wrapping_add(delta as u16); self.branch(self.pc.wrapping_add(delta as u16));
} }
} }
(_, _, 1, Icode::JCN) => { (_, _, 1, Icode::JCN) => {
@ -307,19 +375,19 @@ impl Uxn {
let addr = wst.borrow_mut().pop2()?; let addr = wst.borrow_mut().pop2()?;
let cnd = wst.borrow_mut().pop1()?; let cnd = wst.borrow_mut().pop1()?;
if cnd != 0 { if cnd != 0 {
self.pc = addr; self.branch(addr);
} }
} }
(_, _, 0, Icode::JSR) => { (_, _, 0, Icode::JSR) => {
// JSR1 addr8 (relative) // JSR1 addr8 (relative)
rst.borrow_mut().push2(self.pc)?; rst.borrow_mut().push2(self.pc)?;
let delta = wst.borrow_mut().pop1()? as i8; let delta = wst.borrow_mut().pop1()? as i8;
self.pc = self.pc.wrapping_add(delta as u16); self.branch(self.pc.wrapping_add(delta as u16));
} }
(_, _, 1, Icode::JSR) => { (_, _, 1, Icode::JSR) => {
// JSR2 addr16 (absolute) // JSR2 addr16 (absolute)
rst.borrow_mut().push2(self.pc)?; rst.borrow_mut().push2(self.pc)?;
self.pc = wst.borrow_mut().pop2()?; self.branch(wst.borrow_mut().pop2()?);
} }
(_, _, _, Icode::STH) => { (_, _, _, Icode::STH) => {
// STH a // STH a
@ -345,11 +413,6 @@ impl Uxn {
// STR val addr8 -- // STR val addr8 --
let delta = wst.borrow_mut().pop1()?; let delta = wst.borrow_mut().pop1()?;
let addr = self.pc.wrapping_add(delta as u16); let addr = self.pc.wrapping_add(delta as u16);
println!(
"STR] Got delta {}, effective store address #{:04x}",
delta, addr
);
store(addr, pop(wst)?)?; store(addr, pop(wst)?)?;
} }
(_, _, _, Icode::LDA) => { (_, _, _, Icode::LDA) => {
@ -366,12 +429,14 @@ impl Uxn {
// DEI port8 -- a8 // DEI port8 -- a8
let mut wst = wst.borrow_mut(); let mut wst = wst.borrow_mut();
let port = wst.pop1()?; let port = wst.pop1()?;
eprintln!(" DEI got port {:02X}", port);
wst.push1(self.dei1(port)?)?; wst.push1(self.dei1(port)?)?;
} }
(_, _, 1, Icode::DEI) => { (_, _, 1, Icode::DEI) => {
// DEI2 port8 -- a16 // DEI2 port8 -- a16
let mut wst = wst.borrow_mut(); let mut wst = wst.borrow_mut();
let port = wst.pop1()?; let port = wst.pop1()?;
eprintln!(" DEI2 got port {:02X}", port);
wst.push2(self.dei2(port)?)?; wst.push2(self.dei2(port)?)?;
} }
(_, _, 0, Icode::DEO) => { (_, _, 0, Icode::DEO) => {
@ -379,37 +444,39 @@ impl Uxn {
let mut wst = wst.borrow_mut(); let mut wst = wst.borrow_mut();
let port = wst.pop1()?; let port = wst.pop1()?;
let val = wst.pop1()?; let val = wst.pop1()?;
self.deo1(port, val); self.deo1(port, val).unwrap();
} }
(_, _, 1, Icode::DEO) => { (_, _, 1, Icode::DEO) => {
// DEO2 a16 port8 -- // DEO2 a16 port8 --
let mut wst = wst.borrow_mut(); let mut wst = wst.borrow_mut();
let port = wst.pop1()?; let port = wst.pop1()?;
let val = wst.pop2()?; let val = wst.pop2()?;
self.deo2(port, val); self.deo2(port, val).unwrap();
} }
(_, _, _, Icode::ADD) => { (_, _, _, Icode::ADD) => {
// ADD a b -- c // ADD a b -- c
let b = pop(wst.clone())?; let b = pop(wst.clone())?;
let a = pop(wst.clone())?; let a = pop(wst.clone())?;
push(wst.clone(), a + b)?; push(wst.clone(), a.wrapping_add(b))?;
} }
(_, _, _, Icode::SUB) => { (_, _, _, Icode::SUB) => {
// SUB a b -- c // SUB a b -- c
let b = pop(wst.clone())?; let b = pop(wst.clone())?;
let a = pop(wst.clone())?; let a = pop(wst.clone())?;
push(wst.clone(), a - b)?; push(wst.clone(), a.wrapping_sub(b))?;
} }
(_, _, _, Icode::MUL) => { (_, _, _, Icode::MUL) => {
// MUL a b -- c // MUL a b -- c
let b = pop(wst.clone())?; let b = pop(wst.clone())?;
let a = pop(wst.clone())?; let a = pop(wst.clone())?;
push(wst.clone(), a * b)?; push(wst.clone(), a.wrapping_mul(b))?;
} }
(_, _, _, Icode::DIV) => { (_, _, _, Icode::DIV) => {
// DIV a b -- c // DIV a b -- c
self.debug();
let b = pop(wst.clone())?; let b = pop(wst.clone())?;
let a = pop(wst.clone())?; let a = pop(wst.clone())?;
eprintln!(" DIV {:04X} {:04X}", a, b);
push(wst.clone(), a / b)?; push(wst.clone(), a / b)?;
} }
(_, _, _, Icode::AND) => { (_, _, _, Icode::AND) => {
@ -451,11 +518,6 @@ impl Uxn {
if executed == limit { if executed == limit {
return Err(UxnError::ExecutionLimit(executed)); return Err(UxnError::ExecutionLimit(executed));
} }
if self.is_halted() {
break;
}
executed += 1; executed += 1;
match self.step() { match self.step() {
@ -463,6 +525,5 @@ impl Uxn {
Ok(()) => continue, Ok(()) => continue,
}; };
} }
Ok(())
} }
} }

View file

@ -722,12 +722,13 @@ fn test_sta2() {
fn test_dei() { fn test_dei() {
nofmt::pls! { nofmt::pls! {
let mut vm = Uxn::of1(&[ let mut vm = Uxn::of1(&[
Icode::LIT, 0x30, Icode::DEI, Icode::BRK, Icode::LIT, 0x30,
Icode::DEI,
Icode::BRK,
]); ]);
let dev = Rc::new(RefCell::new(BuffDevice::new())); let dev = Rc::new(RefCell::new(BuffDevice::new()));
vm.deo1(0x30, 0xFF).unwrap();
vm.deo1(0x31, 0xEE).unwrap();
vm.devices[3] = dev.clone(); vm.devices[3] = dev.clone();
vm.deo1(0x30, 0xFF).unwrap();
} }
assert_eq!(vm.run(64), Err(UxnError::Break)); assert_eq!(vm.run(64), Err(UxnError::Break));
assert_eq!(vm.wst.borrow().idx(), 1); assert_eq!(vm.wst.borrow().idx(), 1);
@ -738,11 +739,13 @@ fn test_dei() {
fn test_dei2() { fn test_dei2() {
nofmt::pls! { nofmt::pls! {
let mut vm = Uxn::of1(&[ let mut vm = Uxn::of1(&[
Icode::LIT, 0x30, Icode::DEI | Icode::SHORT, Icode::BRK, Icode::LIT, 0x30,
Icode::DEI | Icode::SHORT,
Icode::BRK,
]); ]);
let dev = Rc::new(RefCell::new(BuffDevice::new())); let dev = Rc::new(RefCell::new(BuffDevice::new()));
vm.deo2(0x30, 0xFFEE).unwrap();
vm.devices[3] = dev.clone(); vm.devices[3] = dev.clone();
vm.deo2(0x30, 0xFFEE).unwrap();
} }
assert_eq!(vm.run(64), Err(UxnError::Break)); assert_eq!(vm.run(64), Err(UxnError::Break));
assert_eq!(vm.wst.borrow().idx(), 2); assert_eq!(vm.wst.borrow().idx(), 2);