Initial checkin

This commit is contained in:
Seth Morabito 2018-11-17 10:42:18 -08:00
commit 16f43c9281
8 changed files with 2429 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
**/*.rs.bk
Cargo.lock
.idea

8
Cargo.toml Normal file
View File

@ -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"

48
README.md Normal file
View File

@ -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.

230
src/bus.rs Normal file
View File

@ -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());
}
}

1724
src/cpu.rs Normal file

File diff suppressed because it is too large Load Diff

126
src/err.rs Normal file
View File

@ -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)
}
}

9
src/lib.rs Normal file
View File

@ -0,0 +1,9 @@
#![feature(nll)]
pub mod bus;
pub mod cpu;
pub mod err;
pub mod mem;
#[macro_use]
extern crate lazy_static;

280
src/mem.rs Normal file
View File

@ -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());
}
}