Rearranged into a cargo workspace
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
use crate::memory::PeekPoke;
|
||||
use crate::word::Word;
|
||||
|
||||
pub trait Device {
|
||||
fn tick(&mut self);
|
||||
fn reset(&mut self);
|
||||
}
|
||||
|
||||
pub struct Bus<A, B> {
|
||||
start: Word,
|
||||
end: Word,
|
||||
device: A,
|
||||
rest: B,
|
||||
}
|
||||
|
||||
impl<A, B> Bus<A, B> {
|
||||
fn new(start: u32, end: u32, device: A, rest: B) -> Self {
|
||||
Self {
|
||||
start: start.into(),
|
||||
end: end.into(),
|
||||
device,
|
||||
rest,
|
||||
}
|
||||
}
|
||||
|
||||
fn at(addr: u32, device: A, rest: B) -> Self {
|
||||
Self::new(addr, addr, device, rest)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: PeekPoke, B: PeekPoke> PeekPoke for Bus<A, B> {
|
||||
fn peek(&self, addr: Word) -> u8 {
|
||||
if addr >= self.start && addr <= self.end {
|
||||
self.device.peek(addr - self.start)
|
||||
} else {
|
||||
self.rest.peek(addr)
|
||||
}
|
||||
}
|
||||
|
||||
fn poke(&mut self, addr: Word, val: u8) {
|
||||
if addr >= self.start && addr <= self.end {
|
||||
self.device.poke(addr - self.start, val)
|
||||
} else {
|
||||
self.rest.poke(addr, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Device, B: Device> Device for Bus<A, B> {
|
||||
fn tick(&mut self) {
|
||||
self.device.tick();
|
||||
self.rest.tick();
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.device.reset();
|
||||
self.rest.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::memory::PeekPokeExt;
|
||||
|
||||
struct TestDevice(i32);
|
||||
impl Device for TestDevice {
|
||||
fn tick(&mut self) {
|
||||
self.0 += 1
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
self.0 = 10
|
||||
}
|
||||
}
|
||||
|
||||
struct ArrayDevice([u8; 10]);
|
||||
impl PeekPoke for ArrayDevice {
|
||||
fn peek(&self, addr: Word) -> u8 {
|
||||
self.0[usize::from(addr)]
|
||||
}
|
||||
fn poke(&mut self, addr: Word, val: u8) {
|
||||
self.0[usize::from(addr)] = val
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tick() {
|
||||
let device1 = TestDevice(5);
|
||||
let device2 = TestDevice(6);
|
||||
let device3 = TestDevice(7);
|
||||
let mut bus = Bus::at(5, device1, Bus::at(6, device2, device3));
|
||||
|
||||
for _ in 0..5 {
|
||||
bus.tick()
|
||||
}
|
||||
assert_eq!(bus.device.0, 10);
|
||||
assert_eq!(bus.rest.device.0, 11);
|
||||
assert_eq!(bus.rest.rest.0, 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reset() {
|
||||
let device1 = TestDevice(5);
|
||||
let device2 = TestDevice(6);
|
||||
let mut bus = Bus::at(5, device1, device2);
|
||||
|
||||
bus.reset();
|
||||
assert_eq!(bus.device.0, 10);
|
||||
assert_eq!(bus.rest.0, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poke_peek() {
|
||||
let mut bus = Bus::new(5, 10, ArrayDevice([0u8; 10]), ArrayDevice([0u8; 10]));
|
||||
bus.poke8(2, 2); // Goes into the 2nd device
|
||||
bus.poke8(6, 6); // Goes into the first device...
|
||||
assert_eq!(bus.device.0[1], 6); // At index 1
|
||||
assert_eq!(bus.rest.0[2], 2); // Second device gets the other write
|
||||
|
||||
// Neither device sees the other's write:
|
||||
assert_eq!(bus.device.0[2], 0);
|
||||
assert_eq!(bus.rest.0[1], 0);
|
||||
|
||||
assert_eq!(bus.peek8(2), 2); // Reading from the first device
|
||||
assert_eq!(bus.peek8(6), 6); // And the second
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,621 @@
|
||||
use crate::memory::PeekPoke;
|
||||
use crate::memory::{Memory, PeekPokeExt};
|
||||
use crate::opcodes::InvalidOpcode;
|
||||
use crate::opcodes::Opcode;
|
||||
use crate::word::Word;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub struct CPU {
|
||||
memory: Memory, // Main memory, all of it
|
||||
pc: Word, // program counter, address of the low byte of the instruction
|
||||
dp: Word, // data pointer, address of the low byte of one cell above the data stack
|
||||
sp: Word, // stack pointer, address of the low byte of the return stack
|
||||
iv: Word, // 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<Word>,
|
||||
length: u8,
|
||||
}
|
||||
|
||||
impl PeekPoke for CPU {
|
||||
fn peek(&self, addr: Word) -> u8 {
|
||||
self.memory.peek(addr)
|
||||
}
|
||||
fn poke(&mut self, addr: Word, val: u8) {
|
||||
self.memory.poke(addr, val)
|
||||
}
|
||||
}
|
||||
|
||||
impl CPU {
|
||||
pub fn new(memory: Memory) -> Self {
|
||||
Self {
|
||||
memory,
|
||||
pc: 1024.into(),
|
||||
dp: 256.into(),
|
||||
sp: 1024.into(),
|
||||
iv: 1024.into(),
|
||||
int_enabled: false,
|
||||
halted: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub 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<Word>>(&mut self, word: A) {
|
||||
self.memory.poke24(self.dp, word);
|
||||
self.dp += 3;
|
||||
}
|
||||
|
||||
fn push_call<A: Into<Word>>(&mut self, word: A) {
|
||||
self.sp -= 3;
|
||||
self.memory.poke24(self.sp, word);
|
||||
}
|
||||
|
||||
fn pop_data(&mut self) -> Word {
|
||||
self.dp -= 3;
|
||||
self.memory.peek24(self.dp)
|
||||
}
|
||||
|
||||
fn pop_call(&mut self) -> Word {
|
||||
let val = self.memory.peek24(self.sp);
|
||||
self.sp += 3;
|
||||
val
|
||||
}
|
||||
|
||||
fn peek_call(&self) -> Word {
|
||||
self.memory.peek24(self.sp)
|
||||
}
|
||||
|
||||
fn peek_data(&self) -> Word {
|
||||
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,
|
||||
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 <<= 8 * n;
|
||||
arg += b;
|
||||
}
|
||||
Ok(Instruction {
|
||||
opcode,
|
||||
arg: Some(Word::from(arg)),
|
||||
length: arg_length + 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(&mut self, instruction: Instruction) -> Word {
|
||||
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(y > x),
|
||||
Opcode::Lt => self.push_data(y < x),
|
||||
Opcode::Agt => self.push_data(i32::from(y) > i32::from(x)),
|
||||
Opcode::Alt => self.push_data(i32::from(y) < i32::from(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..u32::from(x).clamp(0, 24) {
|
||||
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.poke8(x, y.to_bytes()[0]),
|
||||
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 + i32::from(x);
|
||||
}
|
||||
}
|
||||
Opcode::Brnz => {
|
||||
if y != 0 {
|
||||
return self.pc + i32::from(x);
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
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(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 - (i32::from(index) + 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 = i32::from(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.peek8(x))
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
if self.halted { return }
|
||||
|
||||
match self.fetch() {
|
||||
Ok(instr) => {
|
||||
//println!("Instr: {}", instr.opcode);
|
||||
self.pc = self.execute(instr)
|
||||
}
|
||||
|
||||
Err(invalid_opcode) => {
|
||||
self.halted = true;
|
||||
println!("Error: {}", invalid_opcode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
self.halted = false
|
||||
}
|
||||
|
||||
pub fn running(&self) -> bool {
|
||||
!self.halted
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use Opcode::*;
|
||||
|
||||
impl CPU {
|
||||
fn get_stack(&self) -> Vec<Word> {
|
||||
let mut v = Vec::new();
|
||||
let mut curr = Word::from(256);
|
||||
while curr < self.dp {
|
||||
v.push(self.memory.peek24(curr));
|
||||
curr += 3
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
fn get_call(&self) -> Vec<Word> {
|
||||
let mut v = Vec::new();
|
||||
let mut curr = Word::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: Word,
|
||||
) {
|
||||
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<Word>,
|
||||
{
|
||||
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(Word::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(Word::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);
|
||||
assert_eq!(cpu.dp, 2000)
|
||||
},
|
||||
);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
#[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);
|
||||
assert_eq!(cpu.dp, 256 + 6);
|
||||
|
||||
assert_eq!(cpu.pop_data(), 45);
|
||||
assert_eq!(cpu.pop_data(), 37);
|
||||
assert_eq!(cpu.dp, 256);
|
||||
|
||||
assert_eq!(cpu.pop_call(), 34);
|
||||
assert_eq!(cpu.pop_call(), 12);
|
||||
assert_eq!(cpu.sp, 1024);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cpu_fetch() {
|
||||
let mut cpu = CPU::new(Memory::default());
|
||||
cpu.memory.poke8(0x400, 0x01); // nop 1 arg
|
||||
cpu.memory.poke8(0x401, 0x02); // 2
|
||||
cpu.memory.poke8(0x402, 0x07); // add 3 arg
|
||||
cpu.memory.poke24(0x403, 0x123456); // 3-byte arg
|
||||
cpu.memory.poke8(0x406, 29 << 2); // hlt
|
||||
cpu.memory.poke8(0x407, 0xfc); // gibberish
|
||||
|
||||
assert_eq!(
|
||||
cpu.fetch(),
|
||||
Ok(Instruction {
|
||||
opcode: Opcode::Nop,
|
||||
arg: Some(Word::from(2)),
|
||||
length: 2
|
||||
})
|
||||
);
|
||||
|
||||
cpu.pc = 0x402.into();
|
||||
assert_eq!(
|
||||
cpu.fetch(),
|
||||
Ok(Instruction {
|
||||
opcode: Opcode::Add,
|
||||
arg: Some(Word::from(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)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod cpu;
|
||||
pub mod memory;
|
||||
pub mod opcodes;
|
||||
pub mod word;
|
||||
@@ -0,0 +1,120 @@
|
||||
use crate::word::Word;
|
||||
use crate::word::MEM_SIZE;
|
||||
use rand::Rng;
|
||||
|
||||
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<Word> for Memory {
|
||||
type Output = u8;
|
||||
fn index(&self, index: Word) -> &Self::Output {
|
||||
&self.0[usize::from(index)]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<Word> for Memory {
|
||||
fn index_mut(&mut self, index: Word) -> &mut Self::Output {
|
||||
&mut self.0[usize::from(index)]
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PeekPoke {
|
||||
/// Read a byte from a given address.
|
||||
fn peek(&self, addr: Word) -> u8;
|
||||
|
||||
/// Write a byte to a given address.
|
||||
fn poke(&mut self, addr: Word, val: u8);
|
||||
|
||||
/// Read a slice, starting from a given address.
|
||||
fn peek_slice(&self, addr: Word, buffer: &mut [u8]) {
|
||||
for (i, byte) in buffer.iter_mut().enumerate() {
|
||||
*byte = self.peek(addr + i);
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a slice, starting at a given address.
|
||||
fn poke_slice(&mut self, addr: Word, buffer: &[u8]) {
|
||||
for (i, byte) in buffer.iter().enumerate() {
|
||||
self.poke(addr + i, *byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PeekPokeExt {
|
||||
fn peek8<A: Into<Word>>(&self, addr: A) -> u8;
|
||||
fn poke8<A: Into<Word>, V: Into<u8>>(&mut self, addr: A, val: V);
|
||||
|
||||
fn peek24<A: Into<Word>>(&self, addr: A) -> Word;
|
||||
fn poke24<A: Into<Word>, V: Into<Word>>(&mut self, addr: A, val: V);
|
||||
}
|
||||
|
||||
impl<T: PeekPoke> PeekPokeExt for T {
|
||||
fn peek8<A: Into<Word>>(&self, addr: A) -> u8 {
|
||||
self.peek(addr.into())
|
||||
}
|
||||
|
||||
fn poke8<A: Into<Word>, V: Into<u8>>(&mut self, addr: A, val: V) {
|
||||
self.poke(addr.into(), val.into());
|
||||
}
|
||||
|
||||
fn peek24<A: Into<Word>>(&self, addr: A) -> Word {
|
||||
let mut bytes = [0u8; 3];
|
||||
self.peek_slice(addr.into(), &mut bytes);
|
||||
bytes.into()
|
||||
}
|
||||
|
||||
fn poke24<A: Into<Word>, V: Into<Word>>(&mut self, addr: A, val: V) {
|
||||
let addr = addr.into();
|
||||
let val = val.into();
|
||||
self.poke_slice(addr, &val.to_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl PeekPoke for Memory {
|
||||
fn peek(&self, addr: Word) -> u8 {
|
||||
self[addr]
|
||||
}
|
||||
fn poke(&mut self, addr: Word, val: u8) {
|
||||
self[addr] = val;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mem_peek_poke() {
|
||||
let mut mem = Memory::default();
|
||||
assert_eq!(mem.peek24(35), 0);
|
||||
mem.poke24(35, 45);
|
||||
assert_eq!(mem.peek24(35), 45);
|
||||
assert_eq!(mem.peek24(36), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mem_word_fns() {
|
||||
let mut mem = Memory::default();
|
||||
mem.poke24(10, 0x123456);
|
||||
assert_eq!(mem.peek8(10), 0x56);
|
||||
assert_eq!(mem.peek8(11), 0x34);
|
||||
assert_eq!(mem.peek8(12), 0x12);
|
||||
assert_eq!(mem.peek24(10), 0x123456);
|
||||
assert_eq!(mem.peek24(11), 0x001234);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
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 Display for Opcode {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Opcode::Nop => { write!(f, "nop") }
|
||||
Opcode::Add => { write!(f, "add") }
|
||||
Opcode::Sub => { write!(f, "sub") }
|
||||
Opcode::Mul => { write!(f, "mul") }
|
||||
Opcode::Div => { write!(f, "div") }
|
||||
Opcode::Mod => { write!(f, "mod") }
|
||||
Opcode::Rand => { write!(f, "rand") }
|
||||
Opcode::And => { write!(f, "and") }
|
||||
Opcode::Or => { write!(f, "or") }
|
||||
Opcode::Xor => { write!(f, "xor") }
|
||||
Opcode::Not => { write!(f, "not") }
|
||||
Opcode::Gt => { write!(f, "gt") }
|
||||
Opcode::Lt => { write!(f, "lt") }
|
||||
Opcode::Agt => { write!(f, "agt") }
|
||||
Opcode::Alt => { write!(f, "alt") }
|
||||
Opcode::Lshift => { write!(f, "lshift") }
|
||||
Opcode::Rshift => { write!(f, "rshift") }
|
||||
Opcode::Arshift => { write!(f, "arshift") }
|
||||
Opcode::Pop => { write!(f, "pop") }
|
||||
Opcode::Dup => { write!(f, "dup") }
|
||||
Opcode::Swap => { write!(f, "swap") }
|
||||
Opcode::Pick => { write!(f, "pick") }
|
||||
Opcode::Rot => { write!(f, "rot") }
|
||||
Opcode::Jmp => { write!(f, "jmp") }
|
||||
Opcode::Jmpr => { write!(f, "jmpr") }
|
||||
Opcode::Call => { write!(f, "call") }
|
||||
Opcode::Ret => { write!(f, "ret") }
|
||||
Opcode::Brz => { write!(f, "brz") }
|
||||
Opcode::Brnz => { write!(f, "brnz") }
|
||||
Opcode::Hlt => { write!(f, "hlt") }
|
||||
Opcode::Load => { write!(f, "load") }
|
||||
Opcode::Loadw => { write!(f, "loadw") }
|
||||
Opcode::Store => { write!(f, "store") }
|
||||
Opcode::Storew => { write!(f, "storew") }
|
||||
Opcode::Inton => { write!(f, "inton") }
|
||||
Opcode::Intoff => { write!(f, "intoff") }
|
||||
Opcode::Setiv => { write!(f, "setiv") }
|
||||
Opcode::Sdp => { write!(f, "sdp") }
|
||||
Opcode::Setsdp => { write!(f, "setsdp") }
|
||||
Opcode::Pushr => { write!(f, "pushr") }
|
||||
Opcode::Popr => { write!(f, "popr") }
|
||||
Opcode::Peekr => { write!(f, "peekr") }
|
||||
Opcode::Debug => { write!(f, "debug") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
// 128k, the amount of memory in a standard Vulcan machine
|
||||
pub const MEM_SIZE: u32 = 128 * 1024;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, Ord)]
|
||||
pub struct Word(u32);
|
||||
|
||||
impl Word {
|
||||
/// Create a `Word` from three bytes.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use vulcan_emu::Word;
|
||||
///
|
||||
/// assert_eq!(Word::from_bytes([0x01, 0x02, 0x03]) == 0x010203);
|
||||
/// ```
|
||||
pub fn from_bytes(bytes: [u8; 3]) -> Self {
|
||||
let [a, b, c] = bytes;
|
||||
Self(u32::from_le_bytes([a, b, c, 0]))
|
||||
}
|
||||
|
||||
/// Convert a `Word` into three bytes.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use vulcan_emu::Word;
|
||||
///
|
||||
/// assert_eq!(Word::from(0x010203).to_bytes(), [0x01, 0x02, 0x03]);
|
||||
/// ```
|
||||
pub fn to_bytes(self) -> [u8; 3] {
|
||||
let [a, b, c, _] = self.0.to_le_bytes();
|
||||
[a, b, c]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Word {
|
||||
fn from(a: u32) -> Self {
|
||||
Self(a & 0xffffff)
|
||||
}
|
||||
}
|
||||
impl From<Word> for u32 {
|
||||
fn from(word: Word) -> Self {
|
||||
word.0
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_from_u32() {
|
||||
assert_eq!(u32::from(Word::from(0x123456u32)), 0x123456u32);
|
||||
assert_eq!(u32::from(Word::from(0x12345678u32)), 0x345678u32);
|
||||
}
|
||||
|
||||
impl From<Word> for i32 {
|
||||
fn from(word: Word) -> Self {
|
||||
if word.0 & 0x800000 != 0 {
|
||||
-(((word.0 ^ 0xffffff) + 1) as i32)
|
||||
} else {
|
||||
word.0 as i32
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<i32> for Word {
|
||||
fn from(value: i32) -> Self {
|
||||
(value as u32).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_from_i32() {
|
||||
assert_eq!(i32::from(Word::from(0x123456i32)), 0x123456i32);
|
||||
assert_eq!(i32::from(Word::from(-555i32)), -555i32);
|
||||
}
|
||||
|
||||
// Perform various conversions using the conversions above
|
||||
macro_rules! convert_via {
|
||||
($big:ty => $little:ty) => {
|
||||
impl From<$big> for Word {
|
||||
fn from(value: $big) -> Self {
|
||||
(value as $little).into()
|
||||
}
|
||||
}
|
||||
impl From<Word> for $big {
|
||||
fn from(value: Word) -> Self {
|
||||
<$little>::from(value) as $big
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
convert_via!(u8 => u32);
|
||||
convert_via!(i8 => i32);
|
||||
|
||||
#[test]
|
||||
fn to_from_u8() {
|
||||
assert_eq!(u8::from(Word::from(0x7fu8)), 0x7fu8);
|
||||
assert_eq!(u8::from(Word::from_bytes([1, 2, 3])), 1u8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_from_i8() {
|
||||
assert_eq!(i8::from(Word::from(-1i8)), -1i8);
|
||||
assert_eq!(i8::from(Word::from_bytes([1, 2, 3])), 1i8);
|
||||
assert_eq!(i8::from(Word::from_bytes([0xff, 0, 0])), -1i8);
|
||||
}
|
||||
|
||||
convert_via!(u16 => u32);
|
||||
convert_via!(i16 => i32);
|
||||
|
||||
convert_via!(u64 => u32);
|
||||
convert_via!(i64 => i32);
|
||||
|
||||
convert_via!(usize => u32);
|
||||
convert_via!(isize => i32);
|
||||
|
||||
impl From<bool> for Word {
|
||||
fn from(value: bool) -> Self {
|
||||
if value {
|
||||
Word::from(1)
|
||||
} else {
|
||||
Word::from(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Word> for bool {
|
||||
fn from(word: Word) -> Self {
|
||||
word.0 != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_from_bool() {
|
||||
assert_eq!(bool::from(Word::from(0)), false);
|
||||
assert_eq!(bool::from(Word::from(1)), true);
|
||||
assert_eq!(bool::from(Word::from(0x123456)), true);
|
||||
|
||||
assert_eq!(Word::from(false), Word::from(0));
|
||||
assert_eq!(Word::from(true), Word::from(1));
|
||||
}
|
||||
|
||||
impl From<[u8; 3]> for Word {
|
||||
fn from(value: [u8; 3]) -> Self {
|
||||
Word::from_bytes(value)
|
||||
}
|
||||
}
|
||||
impl From<Word> for [u8; 3] {
|
||||
fn from(value: Word) -> Self {
|
||||
value.to_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_from_u8_3() {
|
||||
assert_eq!(Word::from([0, 0, 0]), Word::from(0));
|
||||
assert_eq!(Word::from([1, 0, 0]), Word::from(1));
|
||||
assert_eq!(Word::from([0xff, 0xff, 0xff]), Word::from(0xffffff));
|
||||
|
||||
assert_eq!(<[u8; 3]>::from(Word::from(0)), [0, 0, 0]);
|
||||
assert_eq!(<[u8; 3]>::from(Word::from(1)), [1, 0, 0]);
|
||||
assert_eq!(<[u8; 3]>::from(Word::from(0xffffff)), [0xff, 0xff, 0xff]);
|
||||
}
|
||||
|
||||
// Implement negation via i32
|
||||
impl std::ops::Neg for Word {
|
||||
type Output = Word;
|
||||
|
||||
fn neg(self) -> Self::Output {
|
||||
Self::from(-i32::from(self))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! ops {
|
||||
// Implement operations for $target by converting both Word and $target to $target
|
||||
($target:ty) => {
|
||||
ops!($target, $target);
|
||||
};
|
||||
|
||||
// Implement operations for $target by converting both Word and $target to $intermediate
|
||||
($target:ty, $intermediate:ty) => {
|
||||
impl std::ops::Add<$target> for Word {
|
||||
type Output = Word;
|
||||
fn add(self, rhs: $target) -> Self::Output {
|
||||
<$intermediate>::from(self)
|
||||
.overflowing_add(<$intermediate>::from(rhs))
|
||||
.0
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<$target> for Word {
|
||||
type Output = Word;
|
||||
fn sub(self, rhs: $target) -> Self::Output {
|
||||
<$intermediate>::from(self)
|
||||
.overflowing_sub(<$intermediate>::from(rhs))
|
||||
.0
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<$target> for Word {
|
||||
type Output = Word;
|
||||
fn mul(self, rhs: $target) -> Self::Output {
|
||||
<$intermediate>::from(self)
|
||||
.overflowing_mul(<$intermediate>::from(rhs))
|
||||
.0
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div<$target> for Word {
|
||||
type Output = Word;
|
||||
fn div(self, rhs: $target) -> Self::Output {
|
||||
<$intermediate>::from(self)
|
||||
.overflowing_div(<$intermediate>::from(rhs))
|
||||
.0
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Rem<$target> for Word {
|
||||
type Output = Word;
|
||||
fn rem(self, rhs: $target) -> Self::Output {
|
||||
<$intermediate>::from(self)
|
||||
.rem(<$intermediate>::from(rhs))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::BitAnd<$target> for Word {
|
||||
type Output = Word;
|
||||
fn bitand(self, rhs: $target) -> Self::Output {
|
||||
<$intermediate>::from(self)
|
||||
.bitand(<$intermediate>::from(rhs))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::BitOr<$target> for Word {
|
||||
type Output = Word;
|
||||
fn bitor(self, rhs: $target) -> Self::Output {
|
||||
<$intermediate>::from(self)
|
||||
.bitor(<$intermediate>::from(rhs))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::BitXor<$target> for Word {
|
||||
type Output = Word;
|
||||
fn bitxor(self, rhs: $target) -> Self::Output {
|
||||
<$intermediate>::from(self)
|
||||
.bitxor(<$intermediate>::from(rhs))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Shl<$target> for Word {
|
||||
type Output = Word;
|
||||
fn shl(self, rhs: $target) -> Self::Output {
|
||||
<$intermediate>::from(self)
|
||||
.shl(<$intermediate>::from(rhs))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Shr<$target> for Word {
|
||||
type Output = Word;
|
||||
fn shr(self, rhs: $target) -> Self::Output {
|
||||
<$intermediate>::from(self)
|
||||
.shr(<$intermediate>::from(rhs))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::SubAssign<$target> for Word {
|
||||
fn sub_assign(&mut self, rhs: $target) {
|
||||
*self = *self - rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign<$target> for Word {
|
||||
fn add_assign(&mut self, rhs: $target) {
|
||||
*self = *self + rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialOrd<$target> for Word {
|
||||
fn partial_cmp(&self, rhs: &$target) -> Option<std::cmp::Ordering> {
|
||||
<$intermediate>::from(*self).partial_cmp(&<$intermediate>::from(*rhs))
|
||||
}
|
||||
}
|
||||
impl std::cmp::PartialEq<$target> for Word {
|
||||
fn eq(&self, rhs: &$target) -> bool {
|
||||
<$intermediate>::from(*self).eq(&<$intermediate>::from(*rhs))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ops!(Word, u32);
|
||||
|
||||
ops!(u8);
|
||||
ops!(u16);
|
||||
ops!(u32);
|
||||
ops!(u64);
|
||||
ops!(usize);
|
||||
|
||||
ops!(i8);
|
||||
ops!(i16);
|
||||
ops!(i32);
|
||||
ops!(i64);
|
||||
ops!(isize);
|
||||
|
||||
#[test]
|
||||
fn test_address_truncation() {
|
||||
let a: Word = 0x11223344.into();
|
||||
assert_eq!(a, 0x00223344);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_overflows() {
|
||||
let a = Word::from(0xfffffa);
|
||||
assert_eq!(a + 10, Word(4));
|
||||
|
||||
let b = Word::from(3);
|
||||
assert_eq!(b - 10, Word(0xfffff9));
|
||||
|
||||
let mut c = Word::from(5);
|
||||
c += 3;
|
||||
assert_eq!(c, Word(8));
|
||||
|
||||
let mut d = Word::from(5);
|
||||
d -= 3;
|
||||
assert_eq!(d, Word(2));
|
||||
}
|
||||
Reference in New Issue
Block a user