Initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/vulcan-emu.iml" filepath="$PROJECT_DIR$/.idea/vulcan-emu.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+11
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="CPP_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
Generated
+85
@@ -0,0 +1,85 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.117"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
"rand_hc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_hc"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vulcan-emu"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.2+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "vulcan-emu"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand = "0.8.0"
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// 128k, the amount of memory in a standard Vulcan machine
|
||||||
|
pub const MEM_SIZE: u32 = 128 * 1024;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
pub struct Address(u32);
|
||||||
|
|
||||||
|
impl From<Address> for usize {
|
||||||
|
fn from(a: Address) -> Self { (a.0 & (MEM_SIZE-1)) as usize }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for Address {
|
||||||
|
fn from(a: u32) -> Self { Self(a & 0xffffff) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<u32> for Address {
|
||||||
|
fn into(self) -> u32 { self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Add<i32> for Address {
|
||||||
|
type Output = Address;
|
||||||
|
fn add(self, rhs: i32) -> Self::Output {
|
||||||
|
Address::from((self.0 as i32).overflowing_add(rhs).0 as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Sub<i32> for Address {
|
||||||
|
type Output = Address;
|
||||||
|
fn sub(self, rhs: i32) -> Self::Output { self + -rhs }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::SubAssign<i32> for Address {
|
||||||
|
fn sub_assign(&mut self, rhs: i32) { *self = *self - rhs; }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::AddAssign<i32> for Address {
|
||||||
|
fn add_assign(&mut self, rhs: i32) { *self = *self + rhs; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_address_truncation() {
|
||||||
|
let a: Address = 0x11223344.into();
|
||||||
|
assert_eq!(a, 0x00223344.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_addressing_arrays() {
|
||||||
|
let a: usize = Address::from(0xffffff).into();
|
||||||
|
assert_eq!(a, 0x01ffff as usize);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_address_overflows() {
|
||||||
|
let a = Address::from(0xfffffa);
|
||||||
|
assert_eq!(a + 10, Address(4));
|
||||||
|
|
||||||
|
let b = Address::from(3);
|
||||||
|
assert_eq!(b - 10, Address(0xfffff9));
|
||||||
|
|
||||||
|
let mut c = Address::from(5);
|
||||||
|
c += 3;
|
||||||
|
assert_eq!(c, Address(8));
|
||||||
|
|
||||||
|
let mut d = Address::from(5);
|
||||||
|
d -= 3;
|
||||||
|
assert_eq!(d, Address(2));
|
||||||
|
}
|
||||||
+487
@@ -0,0 +1,487 @@
|
|||||||
|
mod memory;
|
||||||
|
mod address;
|
||||||
|
mod opcodes;
|
||||||
|
|
||||||
|
use memory::Memory;
|
||||||
|
use memory::PeekPoke;
|
||||||
|
use address::Address;
|
||||||
|
use opcodes::Opcode;
|
||||||
|
use crate::opcodes::InvalidOpcode;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
struct CPU {
|
||||||
|
memory: Memory, // Main memory, all of it
|
||||||
|
pc: Address, // program counter, address of the low byte of the instruction
|
||||||
|
dp: Address, // data pointer, address of the low byte of one cell above the data stack
|
||||||
|
sp: Address, // stack pointer, address of the low byte of the return stack
|
||||||
|
iv: Address, // interrupt vector
|
||||||
|
int_enabled: bool, // interrupt enable bit
|
||||||
|
halted: bool, // Whether the CPU is halted
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
|
struct Instruction {
|
||||||
|
opcode: Opcode,
|
||||||
|
arg: Option<u32>,
|
||||||
|
length: u8
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CPU {
|
||||||
|
fn new(memory: Memory) -> Self {
|
||||||
|
Self {
|
||||||
|
memory,
|
||||||
|
pc: 1024.into(),
|
||||||
|
dp: 256.into(),
|
||||||
|
sp: 1024.into(),
|
||||||
|
iv: 1024.into(),
|
||||||
|
int_enabled: false,
|
||||||
|
halted: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.pc = 1024.into();
|
||||||
|
self.dp = 256.into();
|
||||||
|
self.sp = 1024.into();
|
||||||
|
self.iv = 1024.into();
|
||||||
|
self.int_enabled = false;
|
||||||
|
self.halted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_data<A: Into<u32>>(&mut self, word: A) {
|
||||||
|
self.memory.poke24(self.dp, word.into());
|
||||||
|
self.dp += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_call<A: Into<u32>>(&mut self, word: A) {
|
||||||
|
self.sp -= 3;
|
||||||
|
self.memory.poke24(self.sp, word.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_data(&mut self) -> u32 {
|
||||||
|
self.dp -= 3;
|
||||||
|
self.memory.peek24(self.dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_call(&mut self) -> u32 {
|
||||||
|
let val = self.memory.peek24(self.sp);
|
||||||
|
self.sp += 3;
|
||||||
|
val
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek_call(&self) -> u32 {
|
||||||
|
self.memory.peek24(self.sp)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek_data(&self) -> u32 {
|
||||||
|
self.memory.peek24(self.dp - 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch(&self) -> Result<Instruction, InvalidOpcode> {
|
||||||
|
let instruction = self.memory.peek(self.pc);
|
||||||
|
match Opcode::try_from(instruction >> 2) {
|
||||||
|
Ok(opcode) => {
|
||||||
|
let arg_length = instruction & 3;
|
||||||
|
if arg_length == 0 {
|
||||||
|
Ok(Instruction {
|
||||||
|
opcode: opcode,
|
||||||
|
arg: None,
|
||||||
|
length: 1
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let mut arg = 0u32;
|
||||||
|
for n in 0..arg_length {
|
||||||
|
let mut b: u32 = self.memory.peek(self.pc + (n + 1) as i32) as u32;
|
||||||
|
b = b << (8 * n);
|
||||||
|
arg += b;
|
||||||
|
}
|
||||||
|
Ok(Instruction {
|
||||||
|
opcode: opcode,
|
||||||
|
arg: Some(arg),
|
||||||
|
length: arg_length + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&mut self, instruction: Instruction) -> Address {
|
||||||
|
if let Some(arg) = instruction.arg {
|
||||||
|
self.push_data(arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if instruction.opcode.is_binary() {
|
||||||
|
let x = self.pop_data();
|
||||||
|
let y = self.pop_data();
|
||||||
|
|
||||||
|
match instruction.opcode {
|
||||||
|
Opcode::Add => { self.push_data(x + y) }
|
||||||
|
Opcode::Sub => { self.push_data(y - x) }
|
||||||
|
Opcode::Mul => { self.push_data(y * x) }
|
||||||
|
Opcode::Div => { self.push_data(y / x) }
|
||||||
|
Opcode::Mod => { self.push_data(y % x) }
|
||||||
|
Opcode::And => { self.push_data(y & x) }
|
||||||
|
Opcode::Or => { self.push_data(y | x) }
|
||||||
|
Opcode::Xor => { self.push_data(y ^ x) }
|
||||||
|
Opcode::Gt => { self.push_data(bool_as_word(y > x)) }
|
||||||
|
Opcode::Lt => { self.push_data(bool_as_word(y < x)) }
|
||||||
|
Opcode::Agt => { self.push_data(bool_as_word(word_as_signed(y) > word_as_signed(x))) }
|
||||||
|
Opcode::Alt => { self.push_data(bool_as_word(word_as_signed(y) < word_as_signed(x))) }
|
||||||
|
Opcode::Lshift => { self.push_data(y << x) }
|
||||||
|
Opcode::Rshift => { self.push_data(y >> x) }
|
||||||
|
Opcode::Arshift => {
|
||||||
|
if y & 0x800000 != 0 {
|
||||||
|
let mut shifted = y;
|
||||||
|
for _ in 0..x {
|
||||||
|
shifted = shifted >> 1 | 0x800000;
|
||||||
|
}
|
||||||
|
self.push_data(shifted)
|
||||||
|
} else {
|
||||||
|
self.push_data(y >> x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Opcode::Swap => {
|
||||||
|
self.push_data(x);
|
||||||
|
self.push_data(y)
|
||||||
|
}
|
||||||
|
Opcode::Store => { self.memory.poke(x, y as u8) }
|
||||||
|
Opcode::Storew => { self.memory.poke24(x, y) }
|
||||||
|
Opcode::Setsdp => {
|
||||||
|
self.dp = x.into();
|
||||||
|
self.sp = y.into()
|
||||||
|
}
|
||||||
|
Opcode::Brz => { if y == 0 { return self.pc + word_as_signed(x) } }
|
||||||
|
Opcode::Brnz => { if y != 0 { return self.pc + word_as_signed(x) } }
|
||||||
|
_ => {} // This can never happen
|
||||||
|
}
|
||||||
|
self.pc + instruction.length as i32
|
||||||
|
} else {
|
||||||
|
match instruction.opcode {
|
||||||
|
Opcode::Nop => { /* No action required */ }
|
||||||
|
Opcode::Rand => {} // TODO remove this whole instruction
|
||||||
|
Opcode::Not => {
|
||||||
|
let x = self.pop_data();
|
||||||
|
self.push_data(bool_as_word(x == 0))
|
||||||
|
}
|
||||||
|
Opcode::Pop => { self.pop_data(); }
|
||||||
|
Opcode::Dup => { self.push_data(self.peek_data()) }
|
||||||
|
Opcode::Pick => {
|
||||||
|
let index = self.pop_data();
|
||||||
|
let val = self.memory.peek24(self.dp - (index as i32 + 1) * 3);
|
||||||
|
self.push_data(val)
|
||||||
|
}
|
||||||
|
Opcode::Rot => {
|
||||||
|
let x = self.pop_data();
|
||||||
|
let y = self.pop_data();
|
||||||
|
let z = self.pop_data();
|
||||||
|
self.push_data(y);
|
||||||
|
self.push_data(x);
|
||||||
|
self.push_data(z)
|
||||||
|
}
|
||||||
|
Opcode::Jmp => { return self.pop_data().into() }
|
||||||
|
Opcode::Jmpr => {
|
||||||
|
let x = word_as_signed(self.pop_data());
|
||||||
|
return self.pc + x
|
||||||
|
}
|
||||||
|
Opcode::Call => {
|
||||||
|
let x = self.pop_data();
|
||||||
|
self.push_call(self.pc + instruction.length as i32);
|
||||||
|
return x.into()
|
||||||
|
}
|
||||||
|
Opcode::Ret => { return self.pop_call().into() }
|
||||||
|
Opcode::Hlt => { self.halted = true }
|
||||||
|
Opcode::Load => {
|
||||||
|
let x = self.pop_data();
|
||||||
|
self.push_data(self.memory.peek(x) as u32)
|
||||||
|
}
|
||||||
|
Opcode::Loadw => {
|
||||||
|
let x = self.pop_data();
|
||||||
|
self.push_data(self.memory.peek24(x))
|
||||||
|
}
|
||||||
|
Opcode::Inton => { self.int_enabled = true }
|
||||||
|
Opcode::Intoff => { self.int_enabled = false }
|
||||||
|
Opcode::Setiv => { self.iv = self.pop_data().into() }
|
||||||
|
Opcode::Sdp => {
|
||||||
|
self.push_data(self.sp);
|
||||||
|
self.push_data(self.dp + 3) // The +3 accounts for the word we're about to push
|
||||||
|
}
|
||||||
|
Opcode::Pushr => {
|
||||||
|
let x = self.pop_data();
|
||||||
|
self.push_call(x)
|
||||||
|
}
|
||||||
|
Opcode::Popr => {
|
||||||
|
let r = self.pop_call();
|
||||||
|
self.push_data(r)
|
||||||
|
}
|
||||||
|
Opcode::Peekr => {
|
||||||
|
let r = self.peek_call();
|
||||||
|
self.push_data(r)
|
||||||
|
}
|
||||||
|
Opcode::Debug => { /* TODO This should print the stack or something */ }
|
||||||
|
_ => {} // This can never happen
|
||||||
|
}
|
||||||
|
self.pc + instruction.length as i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Opcode {
|
||||||
|
fn is_binary(self) -> bool {
|
||||||
|
use Opcode::*;
|
||||||
|
self != Nop && self != Not && self != Rand && self != Pop && self != Dup && self != Pick &&
|
||||||
|
self != Rot && self != Jmp && self != Jmpr && self != Call && self != Ret &&
|
||||||
|
self != Hlt && self != Load && self != Loadw && self != Inton && self != Intoff &&
|
||||||
|
self != Setiv && self != Sdp && self != Pushr && self != Popr && self != Peekr &&
|
||||||
|
self != Debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn word_as_signed(word: u32) -> i32 {
|
||||||
|
if word & 0x800000 != 0 {
|
||||||
|
-(((word ^ 0xffffff) + 1) as i32)
|
||||||
|
} else {
|
||||||
|
word as i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bool_as_word(flag: bool) -> u32 { if flag { 1 } else { 0 } }
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use Opcode::*;
|
||||||
|
|
||||||
|
impl CPU {
|
||||||
|
fn get_stack(&self) -> Vec<u32> {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
let mut curr = Address::from(256);
|
||||||
|
while curr < self.dp {
|
||||||
|
v.push(self.memory.peek24(curr));
|
||||||
|
curr += 3
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_call(&self) -> Vec<u32> {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
let mut curr = Address::from(1024);
|
||||||
|
while curr > self.sp {
|
||||||
|
curr -= 3;
|
||||||
|
v.push(self.memory.peek24(curr));
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn predicate_opcode_test<P, Q>(opcode: Opcode, given: P, pred: Q) where P: FnOnce(&mut CPU), Q: FnOnce(&CPU)
|
||||||
|
{
|
||||||
|
let mut cpu = CPU::new(Memory::default());
|
||||||
|
given(&mut cpu);
|
||||||
|
let new_pc = cpu.execute(Instruction{ opcode: opcode, arg: None, length: 1 });
|
||||||
|
cpu.pc = new_pc;
|
||||||
|
pred(&mut cpu)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simple_opcode_test(given: Vec<u32>, opcode: Opcode, expected: Vec<u32>) {
|
||||||
|
predicate_opcode_test(opcode, |cpu| {
|
||||||
|
for i in given.into_iter() { cpu.push_data(i) }
|
||||||
|
}, |cpu| {
|
||||||
|
assert_eq!(cpu.get_stack(), expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_stack_opcode_test(given: Vec<u32>, given_r: Vec<u32>, opcode: Opcode, expected: Vec<u32>, expected_r: Vec<u32>, pc: Address) {
|
||||||
|
predicate_opcode_test(opcode, |cpu| {
|
||||||
|
for i in given.into_iter() { cpu.push_data(i) }
|
||||||
|
for i in given_r.into_iter() { cpu.push_call(i) }
|
||||||
|
}, |cpu| {
|
||||||
|
assert_eq!(cpu.get_stack(), expected);
|
||||||
|
assert_eq!(cpu.get_call(), expected_r);
|
||||||
|
assert_eq!(pc, cpu.pc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn control_flow_opcode_test<A>(given: Vec<u32>, opcode: Opcode, expected_pc: A) where A: Into<Address> {
|
||||||
|
predicate_opcode_test(opcode, |cpu| {
|
||||||
|
for i in given.into_iter() { cpu.push_data(i) }
|
||||||
|
}, |cpu| {
|
||||||
|
assert_eq!(cpu.pc, expected_pc.into())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn memory_opcode_test(given: Vec<u32>, given_memory: Vec<u8>, opcode: Opcode, expected: Vec<u32>, expected_memory: Option<Vec<u8>>) {
|
||||||
|
predicate_opcode_test(opcode,
|
||||||
|
|cpu| {
|
||||||
|
for i in given.into_iter() { cpu.push_data(i) }
|
||||||
|
for (offset, byte) in given_memory.into_iter().enumerate() {
|
||||||
|
cpu.memory.poke(Address::from(2048 + offset as u32), byte)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|cpu| {
|
||||||
|
if let Some(expected_memory) = expected_memory {
|
||||||
|
for (offset, byte) in expected_memory.into_iter().enumerate() {
|
||||||
|
let actual = cpu.memory.peek(Address::from(2048 + offset as u32));
|
||||||
|
assert_eq!(byte, actual, "At address 2048 + {}", offset)
|
||||||
|
}
|
||||||
|
assert_eq!(cpu.get_stack(), expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_word(val: i32) -> u32 {
|
||||||
|
if val >= 0 {
|
||||||
|
val as u32
|
||||||
|
} else {
|
||||||
|
((-val ^ 0xffffff) + 1) as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_arithmetic() {
|
||||||
|
simple_opcode_test(vec![5, 3], Add, vec![8]);
|
||||||
|
simple_opcode_test(vec![5, 3], Sub, vec![2]);
|
||||||
|
simple_opcode_test(vec![5, 3], Mul, vec![15]);
|
||||||
|
simple_opcode_test(vec![8, 3], Div, vec![2]);
|
||||||
|
simple_opcode_test(vec![10, 3], Mod, vec![1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stack_manipulation() {
|
||||||
|
simple_opcode_test(vec![5], Dup, vec![5, 5]);
|
||||||
|
simple_opcode_test(vec![5, 3], Swap, vec![3, 5]);
|
||||||
|
simple_opcode_test(vec![10, 20, 30, 2], Pick, vec![10, 20, 30, 10]);
|
||||||
|
simple_opcode_test(vec![1, 4, 9], Rot, vec![4, 9, 1]);
|
||||||
|
simple_opcode_test(vec![1, 4, 9], Pop, vec![1, 4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_ops() {
|
||||||
|
control_flow_opcode_test(vec![], Nop, 1025);
|
||||||
|
simple_opcode_test(vec![2], Nop, vec![2]);
|
||||||
|
predicate_opcode_test(Hlt, |_| { }, |cpu| { assert!(cpu.halted) })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_branching_jumping() {
|
||||||
|
control_flow_opcode_test(vec![1234], Jmp, 1234);
|
||||||
|
control_flow_opcode_test(vec![35], Jmpr, 1024 + 35);
|
||||||
|
control_flow_opcode_test(vec![to_word(-3)], Jmpr, 1024 - 3);
|
||||||
|
control_flow_opcode_test(vec![0, 35], Brnz, 1024 + 1);
|
||||||
|
control_flow_opcode_test(vec![17, 35], Brnz, 1024 + 35);
|
||||||
|
control_flow_opcode_test(vec![5, 35], Brz, 1024 + 1);
|
||||||
|
control_flow_opcode_test(vec![0, 35], Brz, 1024 + 35);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_memory() {
|
||||||
|
memory_opcode_test(vec![2048], vec![123], Load, vec![123], None);
|
||||||
|
memory_opcode_test(vec![2048], vec![0x12, 0x34, 0x56], Loadw, vec![0x123456], None);
|
||||||
|
memory_opcode_test(vec![100, 2048], vec![0x12, 0x34, 0x56], Store, vec![], Some(vec![100, 0x34, 0x56]));
|
||||||
|
memory_opcode_test(vec![0x112233, 2048], vec![0x12, 0x34, 0x56], Storew, vec![], Some(vec![0x33, 0x22, 0x11]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_logic() {
|
||||||
|
simple_opcode_test(vec![0b111100, 0b001111], And, vec![0b001100]);
|
||||||
|
simple_opcode_test(vec![0b100, 0b001], Or, vec![0b101]);
|
||||||
|
simple_opcode_test(vec![0b101, 0b011], Xor, vec![0b110]);
|
||||||
|
simple_opcode_test(vec![5], Not, vec![0]);
|
||||||
|
simple_opcode_test(vec![0], Not, vec![1]);
|
||||||
|
simple_opcode_test(vec![5, 3], Gt, vec![1]);
|
||||||
|
simple_opcode_test(vec![5, 7], Gt, vec![0]);
|
||||||
|
simple_opcode_test(vec![5, 3], Lt, vec![0]);
|
||||||
|
simple_opcode_test(vec![5, 7], Lt, vec![1]);
|
||||||
|
simple_opcode_test(vec![5, to_word(-3)], Agt, vec![1]);
|
||||||
|
simple_opcode_test(vec![5, 10], Agt, vec![0]);
|
||||||
|
simple_opcode_test(vec![5, to_word(-3)], Alt, vec![0]);
|
||||||
|
simple_opcode_test(vec![5, 10], Alt, vec![1]);
|
||||||
|
simple_opcode_test(vec![0b1100, 2], Rshift, vec![3]);
|
||||||
|
simple_opcode_test(vec![0b1100, 2], Lshift, vec![0b110000]);
|
||||||
|
simple_opcode_test(vec![0x800010, 2], Arshift, vec![0xe00004]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cpu_call_stack() {
|
||||||
|
call_stack_opcode_test(vec![5000], vec![], Call, vec![], vec![1025], 5000.into());
|
||||||
|
call_stack_opcode_test(vec![], vec![5000], Ret, vec![], vec![], 5000.into());
|
||||||
|
call_stack_opcode_test(vec![], vec![], Sdp, vec![1024, 256 + 6], vec![], 1025.into());
|
||||||
|
predicate_opcode_test(Setsdp,
|
||||||
|
|cpu| {
|
||||||
|
cpu.push_data(1000u32);
|
||||||
|
cpu.push_data(2000u32)
|
||||||
|
},
|
||||||
|
|cpu| {
|
||||||
|
assert_eq!(cpu.sp, 1000.into());
|
||||||
|
assert_eq!(cpu.dp, 2000.into())
|
||||||
|
});
|
||||||
|
call_stack_opcode_test(vec![123], vec![], Pushr, vec![], vec![123], 1025.into());
|
||||||
|
call_stack_opcode_test(vec![], vec![123], Popr, vec![123], vec![], 1025.into());
|
||||||
|
call_stack_opcode_test(vec![], vec![123], Peekr, vec![123], vec![123], 1025.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cpu_new() {
|
||||||
|
let cpu = CPU::new(Memory::default());
|
||||||
|
assert_eq!(cpu.pc, 1024.into());
|
||||||
|
assert_eq!(cpu.halted, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cpu_reset() {
|
||||||
|
let mut cpu = CPU::new(Memory::default());
|
||||||
|
cpu.iv = 12345.into();
|
||||||
|
cpu.reset();
|
||||||
|
assert_eq!(cpu.iv, 1024.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cpu_stacks() {
|
||||||
|
let mut cpu = CPU::new(Memory::default());
|
||||||
|
cpu.push_data(37u32);
|
||||||
|
cpu.push_data(45u32);
|
||||||
|
assert_eq!(cpu.memory.peek24(256), 37);
|
||||||
|
assert_eq!(cpu.memory.peek24(259), 45);
|
||||||
|
|
||||||
|
cpu.push_call(12u32);
|
||||||
|
cpu.push_call(34u32);
|
||||||
|
assert_eq!(cpu.memory.peek24(cpu.sp), 34);
|
||||||
|
assert_eq!(cpu.memory.peek24(cpu.sp + 3), 12);
|
||||||
|
assert_eq!(cpu.sp, (1024 - 6).into());
|
||||||
|
assert_eq!(cpu.dp, (256 + 6).into());
|
||||||
|
|
||||||
|
assert_eq!(cpu.pop_data(), 45);
|
||||||
|
assert_eq!(cpu.pop_data(), 37);
|
||||||
|
assert_eq!(cpu.dp, 256.into());
|
||||||
|
|
||||||
|
assert_eq!(cpu.pop_call(), 34);
|
||||||
|
assert_eq!(cpu.pop_call(), 12);
|
||||||
|
assert_eq!(cpu.sp, 1024.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cpu_fetch() {
|
||||||
|
let mut cpu = CPU::new(Memory::default());
|
||||||
|
cpu.memory.poke(0x400, 0x01); // nop 1 arg
|
||||||
|
cpu.memory.poke(0x401, 0x02); // 2
|
||||||
|
cpu.memory.poke(0x402, 0x07); // add 3 arg
|
||||||
|
cpu.memory.poke24(0x403, 0x123456); // 3-byte arg
|
||||||
|
cpu.memory.poke(0x406, 29 << 2); // hlt
|
||||||
|
cpu.memory.poke(0x407, 0xfc); // gibberish
|
||||||
|
|
||||||
|
assert_eq!(cpu.fetch(), Ok(Instruction { opcode: Opcode::Nop, arg: Some(2), length: 2 }));
|
||||||
|
|
||||||
|
cpu.pc = 0x402.into();
|
||||||
|
assert_eq!(cpu.fetch(), Ok(Instruction { opcode: Opcode::Add, arg: Some(0x123456), length: 4 }));
|
||||||
|
|
||||||
|
cpu.pc = 0x406.into();
|
||||||
|
assert_eq!(cpu.fetch(), Ok(Instruction { opcode: Opcode::Hlt, arg: None, length: 1 }));
|
||||||
|
|
||||||
|
cpu.pc = 0x407.into();
|
||||||
|
assert_eq!(cpu.fetch(), Err(InvalidOpcode(0x3f)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
use rand::Rng;
|
||||||
|
use crate::address::Address;
|
||||||
|
use crate::address::MEM_SIZE;
|
||||||
|
|
||||||
|
pub struct Memory([u8; MEM_SIZE as usize]);
|
||||||
|
|
||||||
|
impl Default for Memory {
|
||||||
|
fn default() -> Self { Self([0u8; MEM_SIZE as usize]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Rng> From<R> for Memory {
|
||||||
|
fn from(mut rng: R) -> Self {
|
||||||
|
let mut mem = Memory::default();
|
||||||
|
for i in 0..(MEM_SIZE - 1) {
|
||||||
|
mem.0[i as usize] = rng.gen()
|
||||||
|
}
|
||||||
|
mem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Index<Address> for Memory {
|
||||||
|
type Output = u8;
|
||||||
|
fn index(&self, index: Address) -> &Self::Output { &self.0[usize::from(index)] }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::IndexMut<Address> for Memory {
|
||||||
|
fn index_mut(&mut self, index: Address) -> &mut Self::Output { &mut self.0[usize::from(index)] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PeekPoke {
|
||||||
|
fn peek<A: Into<Address>>(&self, addr: A) -> u8;
|
||||||
|
fn poke<A: Into<Address>>(&mut self, addr: A, val: u8);
|
||||||
|
|
||||||
|
fn peek24<A: Into<Address>>(&self, addr: A) -> u32 {
|
||||||
|
let addr = addr.into();
|
||||||
|
(self.peek(addr) as u32) | ((self.peek(addr + 1) as u32) << 8) | ((self.peek(addr + 2) as u32) << 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poke24<A: Into<Address>>(&mut self, addr: A, val: u32) {
|
||||||
|
let addr = addr.into();
|
||||||
|
self.poke(addr, val as u8);
|
||||||
|
self.poke(addr + 1, (val >> 8) as u8);
|
||||||
|
self.poke(addr + 2, (val >> 16) as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PeekPoke for Memory {
|
||||||
|
fn peek<A: Into<Address>>(&self, addr: A) -> u8 { self[addr.into()] }
|
||||||
|
fn poke<A: Into<Address>>(&mut self, addr: A, val: u8) { self[addr.into()] = val; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mem_peek_poke() {
|
||||||
|
let mut mem = Memory::default();
|
||||||
|
assert_eq!(mem.peek(35), 0);
|
||||||
|
mem.poke(35, 45);
|
||||||
|
assert_eq!(mem.peek(35), 45);
|
||||||
|
assert_eq!(mem.peek(36), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mem_word_fns() {
|
||||||
|
let mut mem = Memory::default();
|
||||||
|
mem.poke24(10, 0x123456);
|
||||||
|
assert_eq!(mem.peek(10), 0x56);
|
||||||
|
assert_eq!(mem.peek(11), 0x34);
|
||||||
|
assert_eq!(mem.peek(12), 0x12);
|
||||||
|
assert_eq!(mem.peek24(10), 0x123456);
|
||||||
|
assert_eq!(mem.peek24(11), 0x001234);
|
||||||
|
}
|
||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum Opcode {
|
||||||
|
Nop,
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
Mod,
|
||||||
|
Rand,
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
Xor,
|
||||||
|
Not,
|
||||||
|
Gt,
|
||||||
|
Lt,
|
||||||
|
Agt,
|
||||||
|
Alt,
|
||||||
|
Lshift,
|
||||||
|
Rshift,
|
||||||
|
Arshift,
|
||||||
|
Pop,
|
||||||
|
Dup,
|
||||||
|
Swap,
|
||||||
|
Pick,
|
||||||
|
Rot,
|
||||||
|
Jmp,
|
||||||
|
Jmpr,
|
||||||
|
Call,
|
||||||
|
Ret,
|
||||||
|
Brz,
|
||||||
|
Brnz,
|
||||||
|
Hlt,
|
||||||
|
Load,
|
||||||
|
Loadw,
|
||||||
|
Store,
|
||||||
|
Storew,
|
||||||
|
Inton,
|
||||||
|
Intoff,
|
||||||
|
Setiv,
|
||||||
|
Sdp,
|
||||||
|
Setsdp,
|
||||||
|
Pushr,
|
||||||
|
Popr,
|
||||||
|
Peekr,
|
||||||
|
Debug,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct InvalidOpcode(pub u8);
|
||||||
|
|
||||||
|
impl Display for InvalidOpcode {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Invalid opcode {:#02x}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for InvalidOpcode {}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for Opcode {
|
||||||
|
type Error = InvalidOpcode;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
use Opcode::*;
|
||||||
|
Ok(match value {
|
||||||
|
0 => Nop,
|
||||||
|
1 => Add,
|
||||||
|
2 => Sub,
|
||||||
|
3 => Mul,
|
||||||
|
4 => Div,
|
||||||
|
5 => Mod,
|
||||||
|
6 => Rand,
|
||||||
|
7 => And,
|
||||||
|
8 => Or,
|
||||||
|
9 => Xor,
|
||||||
|
10 => Not,
|
||||||
|
11 => Gt,
|
||||||
|
12 => Lt,
|
||||||
|
13 => Agt,
|
||||||
|
14 => Alt,
|
||||||
|
15 => Lshift,
|
||||||
|
16 => Rshift,
|
||||||
|
17 => Arshift,
|
||||||
|
18 => Pop,
|
||||||
|
19 => Dup,
|
||||||
|
20 => Swap,
|
||||||
|
21 => Pick,
|
||||||
|
22 => Rot,
|
||||||
|
23 => Jmp,
|
||||||
|
24 => Jmpr,
|
||||||
|
25 => Call,
|
||||||
|
26 => Ret,
|
||||||
|
27 => Brz,
|
||||||
|
28 => Brnz,
|
||||||
|
29 => Hlt,
|
||||||
|
30 => Load,
|
||||||
|
31 => Loadw,
|
||||||
|
32 => Store,
|
||||||
|
33 => Storew,
|
||||||
|
34 => Inton,
|
||||||
|
35 => Intoff,
|
||||||
|
36 => Setiv,
|
||||||
|
37 => Sdp,
|
||||||
|
38 => Setsdp,
|
||||||
|
39 => Pushr,
|
||||||
|
40 => Popr,
|
||||||
|
41 => Peekr,
|
||||||
|
42 => Debug,
|
||||||
|
other => return Err(InvalidOpcode(other))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode() {
|
||||||
|
assert_eq!(Opcode::try_from(18), Ok(Opcode::Pop));
|
||||||
|
//assert_eq!(str::fmt("{}", Opcode::try_from(136).unwrap_err()), Err(InvalidOpcode(136)));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user