In the beginning was Word
This commit is contained in:
+5
-4
@@ -61,6 +61,7 @@ impl<A: Device, B: Device> Device for Bus<A, B> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::memory::PeekPokeExt;
|
||||||
|
|
||||||
struct TestDevice(i32);
|
struct TestDevice(i32);
|
||||||
impl Device for TestDevice {
|
impl Device for TestDevice {
|
||||||
@@ -111,8 +112,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_poke_peek() {
|
fn test_poke_peek() {
|
||||||
let mut bus = Bus::new(5, 10, ArrayDevice([0u8; 10]), ArrayDevice([0u8; 10]));
|
let mut bus = Bus::new(5, 10, ArrayDevice([0u8; 10]), ArrayDevice([0u8; 10]));
|
||||||
bus.poke_u32(2, 2); // Goes into the 2nd device
|
bus.poke8(2, 2); // Goes into the 2nd device
|
||||||
bus.poke_u32(6, 6); // Goes into the first device...
|
bus.poke8(6, 6); // Goes into the first device...
|
||||||
assert_eq!(bus.device.0[1], 6); // At index 1
|
assert_eq!(bus.device.0[1], 6); // At index 1
|
||||||
assert_eq!(bus.rest.0[2], 2); // Second device gets the other write
|
assert_eq!(bus.rest.0[2], 2); // Second device gets the other write
|
||||||
|
|
||||||
@@ -120,7 +121,7 @@ mod tests {
|
|||||||
assert_eq!(bus.device.0[2], 0);
|
assert_eq!(bus.device.0[2], 0);
|
||||||
assert_eq!(bus.rest.0[1], 0);
|
assert_eq!(bus.rest.0[1], 0);
|
||||||
|
|
||||||
assert_eq!(bus.peek_u32(2), 2); // Reading from the first device
|
assert_eq!(bus.peek8(2), 2); // Reading from the first device
|
||||||
assert_eq!(bus.peek_u32(6), 6); // And the second
|
assert_eq!(bus.peek8(6), 6); // And the second
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-62
@@ -1,5 +1,5 @@
|
|||||||
use crate::memory::Memory;
|
|
||||||
use crate::memory::PeekPoke;
|
use crate::memory::PeekPoke;
|
||||||
|
use crate::memory::{Memory, PeekPokeExt};
|
||||||
use crate::opcodes::InvalidOpcode;
|
use crate::opcodes::InvalidOpcode;
|
||||||
use crate::opcodes::Opcode;
|
use crate::opcodes::Opcode;
|
||||||
use crate::word::Word;
|
use crate::word::Word;
|
||||||
@@ -19,7 +19,7 @@ pub struct CPU {
|
|||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
struct Instruction {
|
struct Instruction {
|
||||||
opcode: Opcode,
|
opcode: Opcode,
|
||||||
arg: Option<u32>,
|
arg: Option<Word>,
|
||||||
length: u8,
|
length: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,32 +54,32 @@ impl CPU {
|
|||||||
self.halted = true;
|
self.halted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_data<A: Into<u32>>(&mut self, word: A) {
|
fn push_data<A: Into<Word>>(&mut self, word: A) {
|
||||||
self.memory.poke24(self.dp, word.into());
|
self.memory.poke24(self.dp, word);
|
||||||
self.dp += 3;
|
self.dp += 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_call<A: Into<u32>>(&mut self, word: A) {
|
fn push_call<A: Into<Word>>(&mut self, word: A) {
|
||||||
self.sp -= 3;
|
self.sp -= 3;
|
||||||
self.memory.poke24(self.sp, word.into());
|
self.memory.poke24(self.sp, word);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop_data(&mut self) -> u32 {
|
fn pop_data(&mut self) -> Word {
|
||||||
self.dp -= 3;
|
self.dp -= 3;
|
||||||
self.memory.peek24(self.dp)
|
self.memory.peek24(self.dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop_call(&mut self) -> u32 {
|
fn pop_call(&mut self) -> Word {
|
||||||
let val = self.memory.peek24(self.sp);
|
let val = self.memory.peek24(self.sp);
|
||||||
self.sp += 3;
|
self.sp += 3;
|
||||||
val
|
val
|
||||||
}
|
}
|
||||||
|
|
||||||
fn peek_call(&self) -> u32 {
|
fn peek_call(&self) -> Word {
|
||||||
self.memory.peek24(self.sp)
|
self.memory.peek24(self.sp)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn peek_data(&self) -> u32 {
|
fn peek_data(&self) -> Word {
|
||||||
self.memory.peek24(self.dp - 3)
|
self.memory.peek24(self.dp - 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
Ok(Instruction {
|
Ok(Instruction {
|
||||||
opcode,
|
opcode,
|
||||||
arg: Some(arg),
|
arg: Some(Word::from(arg)),
|
||||||
length: arg_length + 1,
|
length: arg_length + 1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -130,16 +130,16 @@ impl CPU {
|
|||||||
Opcode::And => self.push_data(y & x),
|
Opcode::And => self.push_data(y & x),
|
||||||
Opcode::Or => self.push_data(y | x),
|
Opcode::Or => self.push_data(y | x),
|
||||||
Opcode::Xor => self.push_data(y ^ x),
|
Opcode::Xor => self.push_data(y ^ x),
|
||||||
Opcode::Gt => self.push_data(bool_as_word(y > x)),
|
Opcode::Gt => self.push_data(y > x),
|
||||||
Opcode::Lt => self.push_data(bool_as_word(y < x)),
|
Opcode::Lt => self.push_data(y < x),
|
||||||
Opcode::Agt => self.push_data(bool_as_word(word_as_signed(y) > word_as_signed(x))),
|
Opcode::Agt => self.push_data(i32::from(y) > i32::from(x)),
|
||||||
Opcode::Alt => self.push_data(bool_as_word(word_as_signed(y) < word_as_signed(x))),
|
Opcode::Alt => self.push_data(i32::from(y) < i32::from(x)),
|
||||||
Opcode::Lshift => self.push_data(y << x),
|
Opcode::Lshift => self.push_data(y << x),
|
||||||
Opcode::Rshift => self.push_data(y >> x),
|
Opcode::Rshift => self.push_data(y >> x),
|
||||||
Opcode::Arshift => {
|
Opcode::Arshift => {
|
||||||
if y & 0x800000 != 0 {
|
if y & 0x800000 != 0 {
|
||||||
let mut shifted = y;
|
let mut shifted = y;
|
||||||
for _ in 0..x {
|
for _ in 0..u32::from(x).clamp(0, 24) {
|
||||||
shifted = shifted >> 1 | 0x800000;
|
shifted = shifted >> 1 | 0x800000;
|
||||||
}
|
}
|
||||||
self.push_data(shifted)
|
self.push_data(shifted)
|
||||||
@@ -151,23 +151,23 @@ impl CPU {
|
|||||||
self.push_data(x);
|
self.push_data(x);
|
||||||
self.push_data(y)
|
self.push_data(y)
|
||||||
}
|
}
|
||||||
Opcode::Store => self.memory.poke(x.into(), y as u8),
|
Opcode::Store => self.memory.poke8(x, y.to_bytes()[0]),
|
||||||
Opcode::Storew => self.memory.poke24(x.into(), y),
|
Opcode::Storew => self.memory.poke24(x, y),
|
||||||
Opcode::Setsdp => {
|
Opcode::Setsdp => {
|
||||||
self.dp = x.into();
|
self.dp = x.into();
|
||||||
self.sp = y.into()
|
self.sp = y.into()
|
||||||
}
|
}
|
||||||
Opcode::Brz => {
|
Opcode::Brz => {
|
||||||
if y == 0 {
|
if y == 0 {
|
||||||
return self.pc + word_as_signed(x);
|
return self.pc + i32::from(x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::Brnz => {
|
Opcode::Brnz => {
|
||||||
if y != 0 {
|
if y != 0 {
|
||||||
return self.pc + word_as_signed(x);
|
return self.pc + i32::from(x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {} // This can never happen
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
self.pc + instruction.length as i32
|
self.pc + instruction.length as i32
|
||||||
} else {
|
} else {
|
||||||
@@ -176,7 +176,7 @@ impl CPU {
|
|||||||
Opcode::Rand => {} // TODO remove this whole instruction
|
Opcode::Rand => {} // TODO remove this whole instruction
|
||||||
Opcode::Not => {
|
Opcode::Not => {
|
||||||
let x = self.pop_data();
|
let x = self.pop_data();
|
||||||
self.push_data(bool_as_word(x == 0))
|
self.push_data(x == 0)
|
||||||
}
|
}
|
||||||
Opcode::Pop => {
|
Opcode::Pop => {
|
||||||
self.pop_data();
|
self.pop_data();
|
||||||
@@ -184,7 +184,7 @@ impl CPU {
|
|||||||
Opcode::Dup => self.push_data(self.peek_data()),
|
Opcode::Dup => self.push_data(self.peek_data()),
|
||||||
Opcode::Pick => {
|
Opcode::Pick => {
|
||||||
let index = self.pop_data();
|
let index = self.pop_data();
|
||||||
let val = self.memory.peek24(self.dp - (index as i32 + 1) * 3);
|
let val = self.memory.peek24(self.dp - (i32::from(index) + 1) * 3);
|
||||||
self.push_data(val)
|
self.push_data(val)
|
||||||
}
|
}
|
||||||
Opcode::Rot => {
|
Opcode::Rot => {
|
||||||
@@ -197,7 +197,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
Opcode::Jmp => return self.pop_data().into(),
|
Opcode::Jmp => return self.pop_data().into(),
|
||||||
Opcode::Jmpr => {
|
Opcode::Jmpr => {
|
||||||
let x = word_as_signed(self.pop_data());
|
let x = i32::from(self.pop_data());
|
||||||
return self.pc + x;
|
return self.pc + x;
|
||||||
}
|
}
|
||||||
Opcode::Call => {
|
Opcode::Call => {
|
||||||
@@ -209,11 +209,11 @@ impl CPU {
|
|||||||
Opcode::Hlt => self.halted = true,
|
Opcode::Hlt => self.halted = true,
|
||||||
Opcode::Load => {
|
Opcode::Load => {
|
||||||
let x = self.pop_data();
|
let x = self.pop_data();
|
||||||
self.push_data(self.memory.peek(x.into()) as u32)
|
self.push_data(self.memory.peek8(x))
|
||||||
}
|
}
|
||||||
Opcode::Loadw => {
|
Opcode::Loadw => {
|
||||||
let x = self.pop_data();
|
let x = self.pop_data();
|
||||||
self.push_data(self.memory.peek24(x.into()))
|
self.push_data(self.memory.peek24(x))
|
||||||
}
|
}
|
||||||
Opcode::Inton => self.int_enabled = true,
|
Opcode::Inton => self.int_enabled = true,
|
||||||
Opcode::Intoff => self.int_enabled = false,
|
Opcode::Intoff => self.int_enabled = false,
|
||||||
@@ -270,29 +270,13 @@ impl Opcode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use Opcode::*;
|
use Opcode::*;
|
||||||
|
|
||||||
impl CPU {
|
impl CPU {
|
||||||
fn get_stack(&self) -> Vec<u32> {
|
fn get_stack(&self) -> Vec<Word> {
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
let mut curr = Word::from(256);
|
let mut curr = Word::from(256);
|
||||||
while curr < self.dp {
|
while curr < self.dp {
|
||||||
@@ -302,7 +286,7 @@ mod tests {
|
|||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_call(&self) -> Vec<u32> {
|
fn get_call(&self) -> Vec<Word> {
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
let mut curr = Word::from(1024);
|
let mut curr = Word::from(1024);
|
||||||
while curr > self.sp {
|
while curr > self.sp {
|
||||||
@@ -520,8 +504,8 @@ mod tests {
|
|||||||
cpu.push_data(2000u32)
|
cpu.push_data(2000u32)
|
||||||
},
|
},
|
||||||
|cpu| {
|
|cpu| {
|
||||||
assert_eq!(cpu.sp, 1000.into());
|
assert_eq!(cpu.sp, 1000);
|
||||||
assert_eq!(cpu.dp, 2000.into())
|
assert_eq!(cpu.dp, 2000)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
call_stack_opcode_test(vec![123], vec![], Pushr, vec![], vec![123], 1025.into());
|
call_stack_opcode_test(vec![123], vec![], Pushr, vec![], vec![123], 1025.into());
|
||||||
@@ -532,7 +516,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_cpu_new() {
|
fn test_cpu_new() {
|
||||||
let cpu = CPU::new(Memory::default());
|
let cpu = CPU::new(Memory::default());
|
||||||
assert_eq!(cpu.pc, 1024.into());
|
assert_eq!(cpu.pc, 1024);
|
||||||
assert_eq!(cpu.halted, true);
|
assert_eq!(cpu.halted, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,7 +525,7 @@ mod tests {
|
|||||||
let mut cpu = CPU::new(Memory::default());
|
let mut cpu = CPU::new(Memory::default());
|
||||||
cpu.iv = 12345.into();
|
cpu.iv = 12345.into();
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
assert_eq!(cpu.iv, 1024.into());
|
assert_eq!(cpu.iv, 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -549,40 +533,40 @@ mod tests {
|
|||||||
let mut cpu = CPU::new(Memory::default());
|
let mut cpu = CPU::new(Memory::default());
|
||||||
cpu.push_data(37u32);
|
cpu.push_data(37u32);
|
||||||
cpu.push_data(45u32);
|
cpu.push_data(45u32);
|
||||||
assert_eq!(cpu.memory.peek24_u32(256), 37);
|
assert_eq!(cpu.memory.peek24(256), 37);
|
||||||
assert_eq!(cpu.memory.peek24_u32(259), 45);
|
assert_eq!(cpu.memory.peek24(259), 45);
|
||||||
|
|
||||||
cpu.push_call(12u32);
|
cpu.push_call(12u32);
|
||||||
cpu.push_call(34u32);
|
cpu.push_call(34u32);
|
||||||
assert_eq!(cpu.memory.peek24(cpu.sp), 34);
|
assert_eq!(cpu.memory.peek24(cpu.sp), 34);
|
||||||
assert_eq!(cpu.memory.peek24(cpu.sp + 3), 12);
|
assert_eq!(cpu.memory.peek24(cpu.sp + 3), 12);
|
||||||
assert_eq!(cpu.sp, (1024 - 6).into());
|
assert_eq!(cpu.sp, 1024 - 6);
|
||||||
assert_eq!(cpu.dp, (256 + 6).into());
|
assert_eq!(cpu.dp, 256 + 6);
|
||||||
|
|
||||||
assert_eq!(cpu.pop_data(), 45);
|
assert_eq!(cpu.pop_data(), 45);
|
||||||
assert_eq!(cpu.pop_data(), 37);
|
assert_eq!(cpu.pop_data(), 37);
|
||||||
assert_eq!(cpu.dp, 256.into());
|
assert_eq!(cpu.dp, 256);
|
||||||
|
|
||||||
assert_eq!(cpu.pop_call(), 34);
|
assert_eq!(cpu.pop_call(), 34);
|
||||||
assert_eq!(cpu.pop_call(), 12);
|
assert_eq!(cpu.pop_call(), 12);
|
||||||
assert_eq!(cpu.sp, 1024.into());
|
assert_eq!(cpu.sp, 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cpu_fetch() {
|
fn test_cpu_fetch() {
|
||||||
let mut cpu = CPU::new(Memory::default());
|
let mut cpu = CPU::new(Memory::default());
|
||||||
cpu.memory.poke_u32(0x400, 0x01); // nop 1 arg
|
cpu.memory.poke8(0x400, 0x01); // nop 1 arg
|
||||||
cpu.memory.poke_u32(0x401, 0x02); // 2
|
cpu.memory.poke8(0x401, 0x02); // 2
|
||||||
cpu.memory.poke_u32(0x402, 0x07); // add 3 arg
|
cpu.memory.poke8(0x402, 0x07); // add 3 arg
|
||||||
cpu.memory.poke24_u32(0x403, 0x123456); // 3-byte arg
|
cpu.memory.poke24(0x403, 0x123456); // 3-byte arg
|
||||||
cpu.memory.poke_u32(0x406, 29 << 2); // hlt
|
cpu.memory.poke8(0x406, 29 << 2); // hlt
|
||||||
cpu.memory.poke_u32(0x407, 0xfc); // gibberish
|
cpu.memory.poke8(0x407, 0xfc); // gibberish
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cpu.fetch(),
|
cpu.fetch(),
|
||||||
Ok(Instruction {
|
Ok(Instruction {
|
||||||
opcode: Opcode::Nop,
|
opcode: Opcode::Nop,
|
||||||
arg: Some(2),
|
arg: Some(Word::from(2)),
|
||||||
length: 2
|
length: 2
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -592,7 +576,7 @@ mod tests {
|
|||||||
cpu.fetch(),
|
cpu.fetch(),
|
||||||
Ok(Instruction {
|
Ok(Instruction {
|
||||||
opcode: Opcode::Add,
|
opcode: Opcode::Add,
|
||||||
arg: Some(0x123456),
|
arg: Some(Word::from(0x123456)),
|
||||||
length: 4
|
length: 4
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
+27
-28
@@ -1,37 +1,36 @@
|
|||||||
use crate::memory::PeekPoke;
|
use crate::memory::{PeekPoke, PeekPokeExt};
|
||||||
use crate::Word;
|
use crate::Word;
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
struct DisplayRegisters {
|
struct DisplayRegisters {
|
||||||
mode: u8,
|
mode: u8,
|
||||||
screen: u32,
|
screen: Word,
|
||||||
palette: u32,
|
palette: Word,
|
||||||
font: u32,
|
font: Word,
|
||||||
height: u32,
|
height: Word,
|
||||||
width: u32,
|
width: Word,
|
||||||
row_offset: u32,
|
row_offset: Word,
|
||||||
col_offset: u32,
|
col_offset: Word,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DisplayRegisters {
|
impl Default for DisplayRegisters {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
mode: 4,
|
mode: 5,
|
||||||
screen: 0x10000,
|
screen: Word::from(0x10000),
|
||||||
palette: 0x20000 - 0x100,
|
palette: Word::from(0x20000 - 0x100),
|
||||||
font: 0x20000 - 0x100 - 0x2000,
|
font: Word::from(0x20000 - 0x100 - 0x2000),
|
||||||
height: 128,
|
height: Word::from(128),
|
||||||
width: 128,
|
width: Word::from(128),
|
||||||
row_offset: 0,
|
row_offset: Word::from(0),
|
||||||
col_offset: 0,
|
col_offset: Word::from(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_display_registers<P: PeekPoke>(machine: &P, start: Word) -> DisplayRegisters {
|
fn read_display_registers<P: PeekPoke>(machine: &P, start: Word) -> DisplayRegisters {
|
||||||
DisplayRegisters {
|
DisplayRegisters {
|
||||||
mode: machine.peek(start),
|
mode: machine.peek8(start),
|
||||||
screen: machine.peek24(start + 1),
|
screen: machine.peek24(start + 1),
|
||||||
palette: machine.peek24(start + 4),
|
palette: machine.peek24(start + 4),
|
||||||
font: machine.peek24(start + 7),
|
font: machine.peek24(start + 7),
|
||||||
@@ -45,13 +44,13 @@ fn read_display_registers<P: PeekPoke>(machine: &P, start: Word) -> DisplayRegis
|
|||||||
fn init_display_registers<P: PeekPoke>(machine: &mut P, start: Word) {
|
fn init_display_registers<P: PeekPoke>(machine: &mut P, start: Word) {
|
||||||
let dr = DisplayRegisters::default();
|
let dr = DisplayRegisters::default();
|
||||||
machine.poke(start, dr.mode);
|
machine.poke(start, dr.mode);
|
||||||
machine.poke24(start + 1, dr.screen.into());
|
machine.poke24(start + 1, dr.screen);
|
||||||
machine.poke24(start + 4, dr.palette.into());
|
machine.poke24(start + 4, dr.palette);
|
||||||
machine.poke24(start + 7, dr.font.into());
|
machine.poke24(start + 7, dr.font);
|
||||||
machine.poke24(start + 10, dr.height.into());
|
machine.poke24(start + 10, dr.height);
|
||||||
machine.poke24(start + 13, dr.width.into());
|
machine.poke24(start + 13, dr.width);
|
||||||
machine.poke24(start + 16, dr.row_offset.into());
|
machine.poke24(start + 16, dr.row_offset);
|
||||||
machine.poke24(start + 19, dr.col_offset.into());
|
machine.poke24(start + 19, dr.col_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_font<P: PeekPoke>(machine: &mut P) {
|
fn init_font<P: PeekPoke>(machine: &mut P) {
|
||||||
@@ -96,9 +95,9 @@ fn init_palette<P: PeekPoke>(machine: &mut P) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_byte_address((x, y): (u32, u32), reg: DisplayRegisters) -> u32 {
|
fn to_byte_address((x, y): (u32, u32), reg: DisplayRegisters) -> Word {
|
||||||
let row_start = (y + reg.row_offset % reg.height) * reg.width + reg.screen;
|
let row_start = (Word::from(y) + reg.row_offset % reg.height) * reg.width + reg.screen;
|
||||||
((x + reg.col_offset) % reg.width) + row_start
|
((Word::from(x) + reg.col_offset) % reg.width) + row_start
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_direct_high_gfx<P: PeekPoke>(machine: &P, reg: DisplayRegisters, frame: &mut [u8]) {
|
fn draw_direct_high_gfx<P: PeekPoke>(machine: &P, reg: DisplayRegisters, frame: &mut [u8]) {
|
||||||
|
|||||||
+47
-38
@@ -10,13 +10,6 @@ impl Default for Memory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Word> for usize {
|
|
||||||
fn from(w: Word) -> Self {
|
|
||||||
let w: u32 = w.into();
|
|
||||||
(w & (MEM_SIZE - 1)) as usize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Rng> From<R> for Memory {
|
impl<R: Rng> From<R> for Memory {
|
||||||
fn from(mut rng: R) -> Self {
|
fn from(mut rng: R) -> Self {
|
||||||
let mut mem = Memory::default();
|
let mut mem = Memory::default();
|
||||||
@@ -41,32 +34,54 @@ impl std::ops::IndexMut<Word> for Memory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait PeekPoke {
|
pub trait PeekPoke {
|
||||||
|
/// Read a byte from a given address.
|
||||||
fn peek(&self, addr: Word) -> u8;
|
fn peek(&self, addr: Word) -> u8;
|
||||||
|
|
||||||
|
/// Write a byte to a given address.
|
||||||
fn poke(&mut self, addr: Word, val: u8);
|
fn poke(&mut self, addr: Word, val: u8);
|
||||||
|
|
||||||
fn peek24(&self, addr: Word) -> u32 {
|
/// Read a slice, starting from a given address.
|
||||||
(self.peek(addr) as u32)
|
fn peek_slice(&self, addr: Word, buffer: &mut [u8]) {
|
||||||
| ((self.peek(addr + 1) as u32) << 8)
|
for (i, byte) in buffer.iter_mut().enumerate() {
|
||||||
| ((self.peek(addr + 2) as u32) << 16)
|
*byte = self.peek(addr + i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poke24(&mut self, addr: Word, val: u32) {
|
/// Write a slice, starting at a given address.
|
||||||
self.poke(addr, val as u8);
|
fn poke_slice(&mut self, addr: Word, buffer: &[u8]) {
|
||||||
self.poke(addr + 1, (val >> 8) as u8);
|
for (i, byte) in buffer.iter().enumerate() {
|
||||||
self.poke(addr + 2, (val >> 16) as u8);
|
self.poke(addr + i, *byte);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn peek_u32(&self, addr: u32) -> u8 {
|
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())
|
self.peek(addr.into())
|
||||||
}
|
}
|
||||||
fn poke_u32(&mut self, addr: u32, val: u8) {
|
|
||||||
self.poke(addr.into(), val)
|
fn poke8<A: Into<Word>, V: Into<u8>>(&mut self, addr: A, val: V) {
|
||||||
|
self.poke(addr.into(), val.into());
|
||||||
}
|
}
|
||||||
fn peek24_u32(&mut self, addr: u32) -> u32 {
|
|
||||||
self.peek24(addr.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_u32(&mut self, addr: u32, val: u32) {
|
|
||||||
self.poke24(addr.into(), val)
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,26 +101,20 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_mem_peek_poke() {
|
fn test_mem_peek_poke() {
|
||||||
let mut mem = Memory::default();
|
let mut mem = Memory::default();
|
||||||
assert_eq!(mem.peek_u32(35), 0);
|
assert_eq!(mem.peek24(35), 0);
|
||||||
mem.poke_u32(35, 45);
|
mem.poke24(35, 45);
|
||||||
assert_eq!(mem.peek_u32(35), 45);
|
assert_eq!(mem.peek24(35), 45);
|
||||||
assert_eq!(mem.peek_u32(36), 0);
|
assert_eq!(mem.peek24(36), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mem_word_fns() {
|
fn test_mem_word_fns() {
|
||||||
let mut mem = Memory::default();
|
let mut mem = Memory::default();
|
||||||
mem.poke24(10.into(), 0x123456);
|
mem.poke24(10, 0x123456);
|
||||||
assert_eq!(mem.peek_u32(10), 0x56);
|
assert_eq!(mem.peek8(10), 0x56);
|
||||||
assert_eq!(mem.peek_u32(11), 0x34);
|
assert_eq!(mem.peek8(11), 0x34);
|
||||||
assert_eq!(mem.peek_u32(12), 0x12);
|
assert_eq!(mem.peek8(12), 0x12);
|
||||||
assert_eq!(mem.peek24(10.into()), 0x123456);
|
assert_eq!(mem.peek24(10), 0x123456);
|
||||||
assert_eq!(mem.peek24(11.into()), 0x001234);
|
assert_eq!(mem.peek24(11), 0x001234);
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_addressing_arrays() {
|
|
||||||
let a: usize = Word::from(0xffffff).into();
|
|
||||||
assert_eq!(a, 0x01ffff as usize);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+290
-29
@@ -1,58 +1,319 @@
|
|||||||
// 128k, the amount of memory in a standard Vulcan machine
|
// 128k, the amount of memory in a standard Vulcan machine
|
||||||
pub const MEM_SIZE: u32 = 128 * 1024;
|
pub const MEM_SIZE: u32 = 128 * 1024;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Debug, Copy, Clone, Eq, Ord)]
|
||||||
pub struct Word(u32);
|
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 {
|
impl From<u32> for Word {
|
||||||
fn from(a: u32) -> Self {
|
fn from(a: u32) -> Self {
|
||||||
Self(a & 0xffffff)
|
Self(a & 0xffffff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Word> for u32 {
|
impl From<Word> for u32 {
|
||||||
fn from(word: Word) -> Self {
|
fn from(word: Word) -> Self {
|
||||||
word.0
|
word.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Add<i32> for Word {
|
#[test]
|
||||||
type Output = Word;
|
fn to_from_u32() {
|
||||||
fn add(self, rhs: i32) -> Self::Output {
|
assert_eq!(u32::from(Word::from(0x123456u32)), 0x123456u32);
|
||||||
Word::from((self.0 as i32).overflowing_add(rhs).0 as u32)
|
assert_eq!(u32::from(Word::from(0x12345678u32)), 0x345678u32);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Sub<i32> for Word {
|
impl From<Word> for i32 {
|
||||||
type Output = Word;
|
fn from(word: Word) -> Self {
|
||||||
fn sub(self, rhs: i32) -> Self::Output {
|
if word.0 & 0x800000 != 0 {
|
||||||
self + -rhs
|
-(((word.0 ^ 0xffffff) + 1) as i32)
|
||||||
|
} else {
|
||||||
|
word.0 as i32
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl From<i32> for Word {
|
||||||
impl std::ops::Sub<Word> for Word {
|
fn from(value: i32) -> Self {
|
||||||
type Output = Word;
|
(value as u32).into()
|
||||||
fn sub(self, rhs: Word) -> Self::Output {
|
|
||||||
Word(self.0 - rhs.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::SubAssign<i32> for Word {
|
|
||||||
fn sub_assign(&mut self, rhs: i32) {
|
|
||||||
*self = *self - rhs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::AddAssign<i32> for Word {
|
|
||||||
fn add_assign(&mut self, rhs: i32) {
|
|
||||||
*self = *self + rhs;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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]
|
#[test]
|
||||||
fn test_address_truncation() {
|
fn test_address_truncation() {
|
||||||
let a: Word = 0x11223344.into();
|
let a: Word = 0x11223344.into();
|
||||||
assert_eq!(a, 0x00223344.into());
|
assert_eq!(a, 0x00223344);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user