Initial checkin
This commit is contained in:
commit
16f43c9281
|
@ -0,0 +1,4 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
.idea
|
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "dmd_core"
|
||||
version = "0.1.0"
|
||||
authors = ["Seth Morabito <web@loomcom.com>"]
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "^1.2.0"
|
||||
log = "0.4"
|
|
@ -0,0 +1,48 @@
|
|||
# AT&T DMD5620 Core
|
||||
|
||||
Core logic for an AT&T / Teletype DMD 5620 terminal emulator
|
||||
|
||||
## Description
|
||||
|
||||
The AT&T / Teletype DMD 5620 terminal was a portrait display,
|
||||
programmable, windowing terminal produced in the early 1980s. It came
|
||||
out of research pioneered by Rob Pike and Bart Locanthi Jr., of AT&T
|
||||
Bell Labs.
|
||||
|
||||
![DMD 5620 Terminal](https://static.loomcom.com/3b2/5620/dmd5620.jpg)
|
||||
|
||||
This project implements the core logic needed to emulate a DMD 5620
|
||||
terminal, including:
|
||||
|
||||
- ROM
|
||||
- RAM
|
||||
- WE32100 CPU
|
||||
- I/O
|
||||
|
||||
This project is written in Rust, and uses [Neon Bindings](https://github.com/neon-bindings/neon)
|
||||
to compile down to a Node.js library for later inclusion in an Electron
|
||||
JavaScript application that will present the user interface and display
|
||||
drawing area.
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2018, Seth J. Morabito <web@loomcom.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,230 @@
|
|||
use err::BusError;
|
||||
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::cmp;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
|
||||
const MEM_MAX: usize = 1 << 32;
|
||||
|
||||
/// Access Status Code
|
||||
pub enum AccessCode {
|
||||
MoveTranslated,
|
||||
CoprDataWrite,
|
||||
AutoVectorIrqAck,
|
||||
CoprDataFetch,
|
||||
StopAck,
|
||||
CoprBroadcast,
|
||||
CoprStatusFetch,
|
||||
ReadInterlocked,
|
||||
AddressFetch,
|
||||
OperandFetch,
|
||||
Write,
|
||||
IrqAck,
|
||||
IFAfterPCDisc,
|
||||
InstrPrefetch,
|
||||
InstrFetch,
|
||||
NoOp,
|
||||
}
|
||||
|
||||
/// A virtual device on the bus.
|
||||
pub trait Device: Send + Sync + Debug {
|
||||
fn address_ranges(&self) -> &[AddressRange];
|
||||
fn name(&self) -> &str;
|
||||
fn is_read_only(&self) -> bool;
|
||||
fn read_byte(&mut self, address: usize, access: AccessCode) -> Result<u8, BusError>;
|
||||
fn read_half(&mut self, address: usize, access: AccessCode) -> Result<u16, BusError>;
|
||||
fn read_word(&mut self, address: usize, access: AccessCode) -> Result<u32, BusError>;
|
||||
fn write_byte(&mut self, address: usize, val: u8, access: AccessCode) -> Result<(), BusError>;
|
||||
fn write_half(&mut self, address: usize, val: u16, access: AccessCode) -> Result<(), BusError>;
|
||||
fn write_word(&mut self, address: usize, val: u32, access: AccessCode) -> Result<(), BusError>;
|
||||
fn load(&mut self, address: usize, data: &[u8]) -> Result<(), BusError>;
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub struct AddressRange {
|
||||
pub start_address: usize,
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
impl AddressRange {
|
||||
pub fn new(start_address: usize, len: usize) -> AddressRange {
|
||||
AddressRange { start_address, len }
|
||||
}
|
||||
|
||||
pub fn contains(&self, address: usize) -> bool {
|
||||
address >= self.start_address && address < self.start_address + self.len
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Bus<'a> {
|
||||
len: usize,
|
||||
devices: Vec<RefCell<&'a mut Device>>,
|
||||
device_map: HashMap<usize, usize>,
|
||||
}
|
||||
|
||||
impl<'a> Bus<'a> {
|
||||
pub fn new(len: usize) -> Bus<'a> {
|
||||
Bus {
|
||||
len: cmp::min(len, MEM_MAX),
|
||||
devices: Vec::new(),
|
||||
device_map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_device(&mut self, device: &'a mut Device) -> Result<(), BusError> {
|
||||
for range in device.address_ranges() {
|
||||
if range.start_address + range.len > self.len {
|
||||
return Err(BusError::Range);
|
||||
}
|
||||
// Scan to see if there's room.
|
||||
for i in range.start_address..(range.start_address + range.len) {
|
||||
if self.device_map.contains_key(&i) {
|
||||
return Err(BusError::Range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now add the device
|
||||
let offset = self.devices.len();
|
||||
|
||||
// Fill in the bus map with the offset of the given device
|
||||
for range in device.address_ranges() {
|
||||
for i in range.start_address..(range.start_address + range.len) {
|
||||
self.device_map.insert(i, offset);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, move the refrence into the device list.
|
||||
self.devices.push(RefCell::new(device));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the memory at a specified address
|
||||
fn get_device(&mut self, address: usize) -> Result<RefMut<&'a mut Device>, BusError> {
|
||||
let offset = self.device_map.get(&address);
|
||||
match offset {
|
||||
Some(o) => {
|
||||
let dev = self.devices[*o].try_borrow_mut();
|
||||
match dev {
|
||||
Ok(d) => Ok(d),
|
||||
Err(_) => Err(BusError::NoDevice),
|
||||
}
|
||||
}
|
||||
None => Err(BusError::NoDevice),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_byte(&mut self, address: usize, access: AccessCode) -> Result<u8, BusError> {
|
||||
self.get_device(address)?.read_byte(address, access)
|
||||
}
|
||||
|
||||
pub fn read_half(&mut self, address: usize, access: AccessCode) -> Result<u16, BusError> {
|
||||
if address & 1 != 0 {
|
||||
return Err(BusError::Alignment);
|
||||
}
|
||||
self.get_device(address)?.read_half(address, access)
|
||||
}
|
||||
|
||||
pub fn read_word(&mut self, address: usize, access: AccessCode) -> Result<u32, BusError> {
|
||||
if address & 3 != 0 {
|
||||
return Err(BusError::Alignment);
|
||||
}
|
||||
self.get_device(address)?.read_word(address, access)
|
||||
}
|
||||
|
||||
pub fn read_half_unaligned(
|
||||
&mut self,
|
||||
address: usize,
|
||||
access: AccessCode,
|
||||
) -> Result<u16, BusError> {
|
||||
self.get_device(address)?.read_half(address, access)
|
||||
}
|
||||
|
||||
pub fn read_word_unaligned(
|
||||
&mut self,
|
||||
address: usize,
|
||||
access: AccessCode,
|
||||
) -> Result<u32, BusError> {
|
||||
self.get_device(address)?.read_word(address, access)
|
||||
}
|
||||
|
||||
pub fn write_byte(&mut self, address: usize, val: u8) -> Result<(), BusError> {
|
||||
self.get_device(address)?
|
||||
.write_byte(address, val, AccessCode::Write)
|
||||
}
|
||||
|
||||
pub fn write_half(&mut self, address: usize, val: u16) -> Result<(), BusError> {
|
||||
if address & 1 != 0 {
|
||||
return Err(BusError::Alignment);
|
||||
}
|
||||
self.get_device(address)?
|
||||
.write_half(address, val, AccessCode::Write)
|
||||
}
|
||||
|
||||
pub fn write_word(&mut self, address: usize, val: u32) -> Result<(), BusError> {
|
||||
if address & 3 != 0 {
|
||||
return Err(BusError::Alignment);
|
||||
}
|
||||
self.get_device(address)?
|
||||
.write_word(address, val, AccessCode::Write)
|
||||
}
|
||||
|
||||
pub fn load(&mut self, address: usize, data: &[u8]) -> Result<(), BusError> {
|
||||
self.get_device(address)?.load(address, data)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bus::Bus;
|
||||
use mem::Mem;
|
||||
|
||||
#[test]
|
||||
fn should_add_device() {
|
||||
let mut mem1: Mem = Mem::new(0, 0x1000, false);
|
||||
let mut mem2: Mem = Mem::new(0x1000, 0x2000, false);
|
||||
let mut bus: Bus = Bus::new(0x10000);
|
||||
assert!(bus.add_device(&mut mem1).is_ok());
|
||||
assert!(bus.add_device(&mut mem2).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fail_on_overlap() {
|
||||
let mut mem1: Mem = Mem::new(0x800, 0x1000, false);
|
||||
let mut mem2: Mem = Mem::new(0x0, 0x1000, false);
|
||||
let mut bus: Bus = Bus::new(0x10000);
|
||||
assert!(bus.add_device(&mut mem1).is_ok());
|
||||
assert!(bus.add_device(&mut mem2).is_err());
|
||||
assert!(bus.get_device(0x1).is_err());
|
||||
assert!(bus.get_device(0x800).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fail_on_too_long() {
|
||||
let mut mem1 = Mem::new(0, 0x10001, false);
|
||||
let mut bus: Bus = Bus::new(0x10000);
|
||||
assert!(bus.add_device(&mut mem1).is_err());
|
||||
// The memory should not have been added to any addresses
|
||||
assert!(bus.get_device(0).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fail_on_alignment_errors() {
|
||||
let mut mem1 = Mem::new(0, 0x10000, false);
|
||||
let mut bus: Bus = Bus::new(0x10000);
|
||||
bus.add_device(&mut mem1).unwrap();
|
||||
|
||||
assert!(bus.write_byte(0, 0x1f).is_ok());
|
||||
assert!(bus.write_half(0, 0x1f1f).is_ok());
|
||||
assert!(bus.write_word(0, 0x1f1f1f1f).is_ok());
|
||||
assert!(bus.write_half(1, 0x1f1f).is_err());
|
||||
assert!(bus.write_half(2, 0x1f1f).is_ok());
|
||||
assert!(bus.write_word(1, 0x1f1f1f1f).is_err());
|
||||
assert!(bus.write_word(2, 0x1f1f1f1f).is_err());
|
||||
assert!(bus.write_word(3, 0x1f1f1f1f).is_err());
|
||||
assert!(bus.write_word(4, 0x1f1f1f1f).is_ok());
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,126 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CpuException {
|
||||
IllegalOpcode,
|
||||
InvalidDescriptor
|
||||
}
|
||||
|
||||
impl fmt::Display for CpuException {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
CpuException::IllegalOpcode => write!(f, "Illegal Opcode"),
|
||||
CpuException::InvalidDescriptor => write!(f, "Invalid Descriptor"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CpuException {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
CpuException::IllegalOpcode => "illegal opcode",
|
||||
CpuException::InvalidDescriptor => "invalid descriptor",
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
match *self {
|
||||
CpuException::IllegalOpcode => None,
|
||||
CpuException::InvalidDescriptor => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BusError {
|
||||
Init,
|
||||
Read,
|
||||
Write,
|
||||
NoDevice,
|
||||
Range,
|
||||
Permission,
|
||||
Alignment,
|
||||
}
|
||||
|
||||
impl fmt::Display for BusError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
BusError::Init => write!(f, "Could not initialize bus"),
|
||||
BusError::Read => write!(f, "Could not read from bus"),
|
||||
BusError::Write => write!(f, "Could not write to bus"),
|
||||
BusError::NoDevice => write!(f, "No device at address"),
|
||||
BusError::Range => write!(f, "Address out of range"),
|
||||
BusError::Permission => write!(f, "Invalid permission"),
|
||||
BusError::Alignment => write!(f, "Memory Alignment"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BusError {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
BusError::Init => "initialize",
|
||||
BusError::Read => "read",
|
||||
BusError::Write => "store",
|
||||
BusError::NoDevice => "no device",
|
||||
BusError::Range => "out of range",
|
||||
BusError::Permission => "invalid permission",
|
||||
BusError::Alignment => "alignment",
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
match *self {
|
||||
BusError::Init => None,
|
||||
BusError::Read => None,
|
||||
BusError::Write => None,
|
||||
BusError::NoDevice => None,
|
||||
BusError::Range => None,
|
||||
BusError::Permission => None,
|
||||
BusError::Alignment => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CpuError {
|
||||
Exception(CpuException),
|
||||
Bus(BusError),
|
||||
}
|
||||
|
||||
impl fmt::Display for CpuError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
CpuError::Exception(ref e) => e.fmt(f),
|
||||
CpuError::Bus(ref e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CpuError {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
CpuError::Exception(ref e) => e.description(),
|
||||
CpuError::Bus(ref e) => e.description(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
match *self {
|
||||
CpuError::Exception(ref e) => Some(e),
|
||||
CpuError::Bus(ref e) => Some(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CpuException> for CpuError {
|
||||
fn from(err: CpuException) -> CpuError {
|
||||
CpuError::Exception(err)
|
||||
}
|
||||
}
|
||||
impl From<BusError> for CpuError {
|
||||
fn from(err: BusError) -> CpuError {
|
||||
CpuError::Bus(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#![feature(nll)]
|
||||
|
||||
pub mod bus;
|
||||
pub mod cpu;
|
||||
pub mod err;
|
||||
pub mod mem;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
|
@ -0,0 +1,280 @@
|
|||
use bus::*;
|
||||
use err::BusError;
|
||||
|
||||
use std::ops::Index;
|
||||
use std::vec::Vec;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mem {
|
||||
address_ranges: Vec<AddressRange>,
|
||||
ram: Vec<u8>,
|
||||
is_read_only: bool,
|
||||
}
|
||||
|
||||
/// Memory is a Device with a single address range.
|
||||
impl Mem {
|
||||
pub fn new(start_address: usize, len: usize, is_read_only: bool) -> Mem {
|
||||
Mem {
|
||||
address_ranges: vec![AddressRange::new(start_address, len)],
|
||||
ram: vec![0; len],
|
||||
is_read_only,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn address_range(&self) -> &AddressRange {
|
||||
&self.address_ranges[0]
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for Mem {
|
||||
fn address_ranges(&self) -> &[AddressRange] {
|
||||
&self.address_ranges
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
if self.is_read_only {
|
||||
"ROM"
|
||||
} else {
|
||||
"RAM"
|
||||
}
|
||||
}
|
||||
|
||||
fn is_read_only(&self) -> bool {
|
||||
self.is_read_only
|
||||
}
|
||||
|
||||
/// Read from memory at the specified absolute address.
|
||||
fn read_byte(&mut self, address: usize, _: AccessCode) -> Result<u8, BusError> {
|
||||
let offset = address.wrapping_sub(self.address_range().start_address);
|
||||
|
||||
if offset >= self.address_range().len {
|
||||
Err(BusError::Range)
|
||||
} else {
|
||||
Ok(self.ram[offset])
|
||||
}
|
||||
}
|
||||
|
||||
fn read_half(&mut self, address: usize, _: AccessCode) -> Result<u16, BusError> {
|
||||
let offset = address.wrapping_sub(self.address_range().start_address);
|
||||
|
||||
if offset >= self.address_range().len {
|
||||
Err(BusError::Range)
|
||||
} else {
|
||||
Ok(
|
||||
// Byte-swap
|
||||
u16::from(self.ram[offset]) | u16::from(self.ram[offset + 1]).wrapping_shl(8),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_word(&mut self, address: usize, _: AccessCode) -> Result<u32, BusError> {
|
||||
let offset = address.wrapping_sub(self.address_range().start_address);
|
||||
|
||||
if offset >= self.address_range().len {
|
||||
Err(BusError::Range)
|
||||
} else {
|
||||
Ok(
|
||||
// Byte-swap
|
||||
u32::from(self.ram[offset])
|
||||
| u32::from(self.ram[offset + 1]).wrapping_shl(8)
|
||||
| u32::from(self.ram[offset + 2]).wrapping_shl(16)
|
||||
| u32::from(self.ram[offset + 3]).wrapping_shl(24),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Write to memory at the specified absolute address.
|
||||
fn write_byte(&mut self, address: usize, val: u8, _: AccessCode) -> Result<(), BusError> {
|
||||
if self.is_read_only {
|
||||
return Err(BusError::Write);
|
||||
}
|
||||
|
||||
let offset = address.wrapping_sub(self.address_range().start_address);
|
||||
|
||||
if offset >= self.address_range().len {
|
||||
Err(BusError::Range)
|
||||
} else {
|
||||
self.ram[offset] = val;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_half(&mut self, address: usize, val: u16, _: AccessCode) -> Result<(), BusError> {
|
||||
if self.is_read_only {
|
||||
return Err(BusError::Write);
|
||||
}
|
||||
|
||||
let offset = address.wrapping_sub(self.address_range().start_address);
|
||||
|
||||
if offset >= self.address_range().len {
|
||||
Err(BusError::Range)
|
||||
} else {
|
||||
self.ram[offset] = (val & 0xff) as u8;
|
||||
self.ram[offset + 1] = (val.wrapping_shr(8) & 0xff) as u8;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_word(&mut self, address: usize, val: u32, _: AccessCode) -> Result<(), BusError> {
|
||||
if self.is_read_only {
|
||||
return Err(BusError::Write);
|
||||
}
|
||||
|
||||
let offset = address.wrapping_sub(self.address_range().start_address);
|
||||
|
||||
if offset >= self.address_range().len {
|
||||
Err(BusError::Range)
|
||||
} else {
|
||||
self.ram[offset] = (val & 0xff) as u8;
|
||||
self.ram[offset + 1] = (val.wrapping_shr(8) & 0xff) as u8;
|
||||
self.ram[offset + 2] = (val.wrapping_shr(16) & 0xff) as u8;
|
||||
self.ram[offset + 3] = (val.wrapping_shr(24) & 0xff) as u8;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a block of bytes into memory at the specified absolute
|
||||
/// address. Note that "load" can load into read-only memory.
|
||||
fn load(&mut self, address: usize, program: &[u8]) -> Result<(), BusError> {
|
||||
let offset = address.wrapping_sub(self.address_range().start_address);
|
||||
|
||||
if offset.wrapping_add(program.len()) > self.address_range().len {
|
||||
Err(BusError::Range)
|
||||
} else {
|
||||
for (i, byte) in program.iter().enumerate() {
|
||||
self.ram[offset.wrapping_add(i)] = *byte;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Mem {
|
||||
type Output = u8;
|
||||
|
||||
fn index(&self, idx: usize) -> &u8 {
|
||||
&self.ram[idx]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_read_big_endian() {
|
||||
let mut mem = Mem::new(0, 0x1000, false);
|
||||
let data: [u8; 4] = [0x01, 0x02, 0x03, 0x04];
|
||||
let result = mem.load(0, &data);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let a = mem.read_byte(0, AccessCode::AddressFetch).unwrap();
|
||||
let b = mem.read_half(0, AccessCode::AddressFetch).unwrap();
|
||||
let c = mem.read_word(0, AccessCode::AddressFetch).unwrap();
|
||||
|
||||
assert_eq!(a, 0x01);
|
||||
assert_eq!(b, 0x0201);
|
||||
assert_eq!(c, 0x04030201);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_write_big_endian() {
|
||||
let mut mem = Mem::new(0, 0x1000, false);
|
||||
let a: u8 = 0x01;
|
||||
let b: u16 = 0x0102u16;
|
||||
let c: u32 = 0x01020304u32;
|
||||
|
||||
mem.write_byte(0, a, AccessCode::Write).unwrap();
|
||||
assert_eq!(0x01, mem[0]);
|
||||
|
||||
mem.write_half(0, b, AccessCode::Write).unwrap();
|
||||
assert_eq!(0x02, mem[0]);
|
||||
assert_eq!(0x01, mem[1]);
|
||||
|
||||
mem.write_word(0, c, AccessCode::Write).unwrap();
|
||||
assert_eq!(0x04, mem[0]);
|
||||
assert_eq!(0x03, mem[1]);
|
||||
assert_eq!(0x02, mem[2]);
|
||||
assert_eq!(0x01, mem[3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_write_to_read_only_memory() {
|
||||
let mut mem = Mem::new(0, 0x1000, true);
|
||||
assert!(mem.write_byte(0, 0x1f, AccessCode::Write).is_err());
|
||||
assert!(mem.write_half(0, 0x1f1f, AccessCode::Write).is_err());
|
||||
assert!(mem.write_word(0, 0x1f1f1f1f, AccessCode::Write).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loads_program_if_it_fits() {
|
||||
let mut mem = Mem::new(0, 3, false);
|
||||
let program: [u8; 3] = [0x0a, 0x30, 0x1f];
|
||||
let result = mem.load(0, &program);
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Did it actually load?
|
||||
assert_eq!(mem[0], 0x0a);
|
||||
assert_eq!(mem[1], 0x30);
|
||||
assert_eq!(mem[2], 0x1f);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_load_program_if_it_doesnt_fit() {
|
||||
let mut mem = Mem::new(0, 3, false);
|
||||
let program: [u8; 4] = [0x0a, 0x30, 0x1f, 0x1b];
|
||||
let result = mem.load(0, &program);
|
||||
assert!(result.is_err());
|
||||
|
||||
// Did it actually fail to load?
|
||||
assert_eq!(mem[0], 0);
|
||||
assert_eq!(mem[1], 0);
|
||||
assert_eq!(mem[2], 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_write_and_read_memory() {
|
||||
let mut mem = Mem::new(0, 2, false);
|
||||
|
||||
let mut read_result = mem.read_byte(0, AccessCode::AddressFetch);
|
||||
assert!(read_result.is_ok());
|
||||
assert_eq!(0, read_result.unwrap());
|
||||
|
||||
let mut write_result = mem.write_byte(0, 0x01, AccessCode::Write);
|
||||
assert!(write_result.is_ok());
|
||||
|
||||
read_result = mem.read_byte(0, AccessCode::AddressFetch);
|
||||
assert!(read_result.is_ok());
|
||||
assert_eq!(1, read_result.unwrap());
|
||||
|
||||
write_result = mem.write_byte(1, 0x02, AccessCode::Write);
|
||||
assert!(write_result.is_ok());
|
||||
|
||||
read_result = mem.read_byte(1, AccessCode::AddressFetch);
|
||||
assert!(read_result.is_ok());
|
||||
assert_eq!(2, read_result.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_read_or_write_memory_when_out_of_bounds() {
|
||||
let mut mem = Mem::new(0, 2, false);
|
||||
|
||||
let write_result = mem.write_byte(2, 0x03, AccessCode::Write);
|
||||
assert!(write_result.is_err());
|
||||
|
||||
let read_result = mem.read_byte(2, AccessCode::AddressFetch);
|
||||
assert!(read_result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory_access_uses_absolute_addresses() {
|
||||
let mut mem = Mem::new(0x300, 2, false);
|
||||
|
||||
let write_result = mem.write_byte(0x300, 0xfe, AccessCode::Write);
|
||||
assert!(write_result.is_ok());
|
||||
|
||||
let read_result = mem.read_byte(0x300, AccessCode::AddressFetch);
|
||||
assert!(read_result.is_ok());
|
||||
assert_eq!(0xfe, read_result.unwrap());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue