Compare commits

...

12 Commits

Author SHA1 Message Date
Seth Morabito d19f3d4fdb Remove dependency on ringbuffer
This change sees the introduction of a tiny FIFO buffer implementation
in util.rs, in lieu of a dependency on the ringbuffer library. The
reason for this change is largely because the ringbuffer library was
unnecessarily complicated and generic, and could only work with sizes
that were a power of 2.
2022-11-20 10:26:24 -08:00
Seth Morabito 73ea34e619 Use 8 bits per char 2022-09-13 20:01:04 -07:00
Seth Morabito 31f1008458 Dirty bit detection for Video RAM
The DMD library now keeps track of when writes to video RAM occur, and
sets a dirty bit. This dirty bit is reset when the video RAM is
requested by the library user, and can be used to avoid repainting when
video ram has not changed.
2022-09-13 11:40:38 -07:00
Seth Morabito 03d0d36061 Update README and Cargo.toml for 0.7.1 2022-09-11 16:56:16 -07:00
Seth Morabito a7e13a75a0 Fix TX enable
TX Enable should only set the TxRDY bit if there is no character
currently in the TX holding register. Likewise, the TxEMT bit should
only be set if there is no character in the shift register.
2022-09-11 16:53:53 -07:00
Seth Morabito 8ba2b44919 Return Duration for delay calculation
Additionally, we were waiting 2x as long as necessary because
each trip through the TX state machine was waiting two full
character delays.
2022-09-10 20:07:27 -07:00
Seth Morabito 0e8f46ca1a Remove broken BRG update 2022-09-10 20:00:29 -07:00
Seth Morabito 24acb4415d Update README for v0.7.0 2022-09-10 11:50:38 -07:00
Seth Morabito 26abbce067 Incorporate 8;7;3 Firmware
This change adds version 8;7;3 along side version 8;7;5 firmware, so
that the simulated DMD 5620 can run either Version 1.1 or Version 2 DMD
software.  keeping 8;7;5 firmware.
2022-09-10 11:46:48 -07:00
Seth Morabito 11703e05fd Merge branch 'dev.branch' 2022-09-06 14:42:03 -07:00
Seth Morabito d08a5930c5
Merge pull request #1 from mikehaertel/tx-enable-bugfix
duart: don't lose tx_data when toggling CNF_ETX
2022-09-06 14:41:03 -07:00
Mike Haertel 93350a8ee4 duart: don't lose tx_data when toggling CNF_ETX 2022-09-06 13:57:35 -05:00
11 changed files with 4847 additions and 380 deletions

View File

@ -1,7 +1,7 @@
[package]
name = "dmd_core"
description = "AT&T / Teletype DMD 5620 Terminal Emulator - Core Library"
version = "0.6.5"
version = "0.7.1"
authors = ["Seth Morabito <web@loomcom.com>"]
homepage = "https://github.com/sethm/dmd_core"
repository = "https://github.com/sethm/dmd_core"
@ -11,8 +11,11 @@ license = "MIT"
categories = ["simulation"]
[dependencies]
log = { version = "0.4.8", features = ["std"] }
env_logger = "0.9.0"
lazy_static = "~1.4"
libc = "~0.2"
thiserror = "1.0"
[profile.release]
debug = true

View File

@ -27,6 +27,12 @@ however.
## Changelog
0.7.1: This change fixes a critical bug in how the TX register status
bits are set.
0.7.0: The simulated DMD 5620 can now run either 8;7;5 or 8;7;3
firmware.
0.6.4: Tracing now shows correctly decoded instructions, plus raw
bytes decoded from instruction stream. DUART delay calculation
is improved. Effective address is now stored off onto

View File

@ -4,9 +4,9 @@ use crate::duart::Duart;
use crate::err::BusError;
use crate::mem::Mem;
use crate::mouse::Mouse;
use std::io::Write;
use std::{fmt::Debug, fs::OpenOptions};
use std::{fs::File, ops::Range};
use std::fmt::Debug;
use std::ops::Range;
const NVRAM_SIZE: usize = 8192;
@ -63,7 +63,7 @@ pub struct Bus {
vid: Mem, // TODO: Figure out what device this really is
bbram: Mem, // TODO: change to BBRAM when implemented
ram: Mem,
trace_log: Option<File>,
video_ram_dirty: bool,
}
impl Bus {
@ -75,7 +75,7 @@ impl Bus {
vid: Mem::new(0x500000, 0x2, false),
bbram: Mem::new(0x600000, 0x2000, false),
ram: Mem::new(0x700000, mem_size, false),
trace_log: None,
video_ram_dirty: false,
}
}
@ -107,25 +107,16 @@ impl Bus {
Err(BusError::NoDevice(address))
}
pub fn trace_on(&mut self, name: &str) -> std::io::Result<()> {
let mut out = OpenOptions::new().create(true).write(true).open(name)?;
writeln!(out, "TRACE START")?;
self.trace_log = Some(out);
Ok(())
fn video_ram_range(&self) -> Range<usize> {
let vid_register = (u16::from(self.vid[0]) << 8 | u16::from(self.vid[1])) as usize;
let start = vid_register * 4;
let end = start + 0x19000;
start..end
}
pub fn trace_enabled(&self) -> bool {
self.trace_log.is_some()
}
pub fn trace_off(&mut self) {
self.trace_log = None;
}
pub fn trace(&mut self, step: u64, line: &str) {
if let Some(trace_log) = &mut self.trace_log {
let _ = writeln!(trace_log, "{:08}: {}", step, line);
}
fn is_video_ram(&self, address: usize) -> bool {
(0x700000..0x800000).contains(&address)
&& self.video_ram_range().contains(&(address - 0x700000))
}
pub fn read_byte(&mut self, address: usize, access: AccessCode) -> Result<u8, BusError> {
@ -163,6 +154,9 @@ impl Bus {
}
pub fn write_byte(&mut self, address: usize, val: u8) -> Result<(), BusError> {
if self.is_video_ram(address) {
self.video_ram_dirty = true;
}
self.get_device(address)?.write_byte(address, val, AccessCode::Write)
}
@ -170,6 +164,9 @@ impl Bus {
if address & 1 != 0 {
return Err(BusError::Alignment(address));
}
if self.is_video_ram(address) {
self.video_ram_dirty = true;
}
self.get_device(address)?.write_half(address, val, AccessCode::Write)
}
@ -177,6 +174,9 @@ impl Bus {
if address & 3 != 0 {
return Err(BusError::Alignment(address));
}
if self.is_video_ram(address) {
self.video_ram_dirty = true;
}
self.get_device(address)?.write_word(address, val, AccessCode::Write)
}
@ -184,11 +184,14 @@ impl Bus {
self.get_device(address)?.load(address, data)
}
pub fn video_ram(&self) -> &[u8] {
let vid_register = (u16::from(self.vid[0]) << 8 | u16::from(self.vid[1])) as usize;
let start = vid_register * 4;
let end = start + 0x19000;
self.ram.as_slice(start..end)
pub fn video_ram(&mut self) -> &[u8] {
self.video_ram_dirty = false;
let range = self.video_ram_range();
self.ram.as_slice(range)
}
pub fn video_ram_dirty(&self) -> bool {
self.video_ram_dirty
}
pub fn service(&mut self) {
@ -212,20 +215,20 @@ impl Bus {
self.duart.mouse_up(button);
}
pub fn rs232_tx_poll(&mut self) -> Option<u8> {
self.duart.rs232_tx_poll()
pub fn rs232_tx(&mut self) -> Option<u8> {
self.duart.rs232_tx()
}
pub fn kb_tx_poll(&mut self) -> Option<u8> {
self.duart.kb_tx_poll()
pub fn keyboard_tx(&mut self) -> Option<u8> {
self.duart.keyboard_tx()
}
pub fn rx_char(&mut self, char: u8) {
self.duart.rx_char(char);
pub fn rs232_rx(&mut self, c: u8) {
self.duart.rs232_rx(c);
}
pub fn rx_keyboard(&mut self, keycode: u8) {
self.duart.rx_keyboard(keycode);
pub fn keyboard_rx(&mut self, keycode: u8) {
self.duart.keyboard_rx(keycode);
}
pub fn duart_output(&self) -> u8 {

View File

@ -3,6 +3,8 @@
use crate::bus::{AccessCode, Bus};
use crate::err::*;
use crate::instr::*;
use log::trace;
use std::fmt;
///
@ -48,14 +50,6 @@ const IPL_TABLE: [u32; 64] = [
const WE32100_VERSION: u32 = 0x1a;
const HALFWORD_MNEMONIC_COUNT: usize = 11;
macro_rules! trace {
($bus:ident, $steps:expr, $msg:expr) => {
if ($bus.trace_enabled()) {
$bus.trace($steps, $msg)
}
};
}
pub enum ExceptionType {
ExternalMemory,
}
@ -640,7 +634,6 @@ pub struct Cpu {
//
pub r: [u32; 16],
error_context: ErrorContext,
steps: u64,
ir: Instruction,
}
@ -655,7 +648,6 @@ impl Cpu {
Cpu {
r: [0; 16],
error_context: ErrorContext::None,
steps: 0,
ir: Instruction {
opcode: 0,
name: "???",
@ -1034,8 +1026,6 @@ impl Cpu {
#[allow(clippy::cognitive_complexity)]
fn dispatch(&mut self, bus: &mut Bus) -> Result<i32, CpuError> {
self.steps = self.steps.wrapping_add(1);
// Update anything that needs updating.
bus.service();
@ -1043,9 +1033,10 @@ impl Cpu {
let cpu_ipl = (self.r[R_PSW]) >> 13 & 0xf;
if cpu_ipl < IPL_TABLE[(val & 0x3f) as usize] {
trace!(
bus,
self.steps,
&format!("[{:08x}] INTERRUPT 0x{:x}", &self.r[R_PC], (!val) & 0x3f)
"[PC={:08x} PSW={:08x}] INTERRUPT 0x{:04x}",
&self.r[R_PC],
&self.r[R_PSW],
(!val) & 0x3f
);
self.on_interrupt(bus, (!val) & 0x3f);
}
@ -2011,11 +2002,7 @@ impl Cpu {
match self.dispatch(bus) {
Ok(i) => {
// We should have the necessary information to trace after dispatch.
trace!(
bus,
self.steps,
&format!("[PC={:08x} PSW={:08x}] {}", &self.r[R_PC], &self.r[R_PSW], &self.ir)
);
// trace!("[PC={:08x} PSW={:08x}] {}", &self.r[R_PC], &self.r[R_PSW], &self.ir);
self.r[R_PC] = (self.r[R_PC] as i32 + i) as u32
}
Err(CpuError::Bus(BusError::NoDevice(_)))
@ -2526,11 +2513,11 @@ impl Cpu {
}
Data::Half | Data::UHalf => {
self.set_n_flag((val & 0x8000) != 0);
self.set_z_flag(val.trailing_zeros() >= 16);
self.set_z_flag(val as u16 == 0);
}
Data::Byte | Data::SByte => {
self.set_n_flag((val & 0x80) != 0);
self.set_z_flag(val.trailing_zeros() >= 8);
self.set_z_flag(val as u8 == 0);
}
Data::None => {
// Intentionally ignored
@ -2650,10 +2637,6 @@ impl Cpu {
pub fn get_psw(&self) -> u32 {
self.r[R_PSW]
}
pub fn get_steps(&self) -> u64 {
self.steps
}
}
#[cfg(test)]

View File

@ -3,22 +3,24 @@
use crate::bus::{AccessCode, Bus};
use crate::cpu::Cpu;
use crate::err::BusError;
use crate::rom_hi::HI_ROM;
use crate::rom_lo::LO_ROM;
use crate::rom_hi::{HI_ROM_V1, HI_ROM_V2};
use crate::rom_lo::{LO_ROM_V1, LO_ROM_V1_LEN, LO_ROM_V2, LO_ROM_V2_LEN};
use libc::*;
use std::sync::Mutex;
use std::{ffi::CStr, ptr};
use std::ptr;
use std::sync::{Mutex, Once};
lazy_static! {
static ref DMD: Mutex<Dmd> = Mutex::new(Dmd::new());
pub static ref DMD: Mutex<Dmd> = Mutex::new(Dmd::new());
}
// Return vlaues for the C library
// Return values for the C library
const SUCCESS: c_int = 0;
const ERROR: c_int = 1;
const BUSY: c_int = 2;
static INIT: Once = Once::new();
pub struct Dmd {
cpu: Cpu,
bus: Bus,
@ -26,12 +28,17 @@ pub struct Dmd {
impl Default for Dmd {
fn default() -> Self {
Dmd::new()
Self::new()
}
}
impl Dmd {
pub fn new() -> Dmd {
// Never re-init logging
INIT.call_once(|| {
env_logger::init();
});
let cpu = Cpu::new();
let bus = Bus::new(0x100000);
Dmd {
@ -40,26 +47,31 @@ impl Dmd {
}
}
pub fn trace_on(&mut self, name: &str) -> std::io::Result<()> {
self.bus.trace_on(name)
}
pub fn reset(&mut self, version: u8) -> Result<(), BusError> {
match version {
1 => {
self.bus.load(0, &LO_ROM_V1)?;
self.bus.load(LO_ROM_V1_LEN, &HI_ROM_V1)?;
}
_ => {
self.bus.load(0, &LO_ROM_V2)?;
self.bus.load(LO_ROM_V2_LEN, &HI_ROM_V2)?;
}
}
pub fn trace_off(&mut self) {
self.bus.trace_off()
}
pub fn reset(&mut self) -> Result<(), BusError> {
self.bus.load(0, &LO_ROM)?;
self.bus.load(0x10000, &HI_ROM)?;
self.cpu.reset(&mut self.bus)?;
Ok(())
}
pub fn video_ram(&self) -> &[u8] {
pub fn video_ram(&mut self) -> &[u8] {
self.bus.video_ram()
}
pub fn video_ram_dirty(&self) -> bool {
self.bus.video_ram_dirty()
}
pub fn get_pc(&self) -> u32 {
self.cpu.get_pc()
}
@ -100,20 +112,20 @@ impl Dmd {
}
}
pub fn rs232_tx_poll(&mut self) -> Option<u8> {
self.bus.rs232_tx_poll()
pub fn rs232_tx(&mut self) -> Option<u8> {
self.bus.rs232_tx()
}
pub fn kb_tx_poll(&mut self) -> Option<u8> {
self.bus.kb_tx_poll()
pub fn keyboard_tx(&mut self) -> Option<u8> {
self.bus.keyboard_tx()
}
pub fn rx_char(&mut self, character: u8) {
self.bus.rx_char(character);
pub fn rs232_rx(&mut self, c: u8) {
self.bus.rs232_rx(c);
}
pub fn rx_keyboard(&mut self, keycode: u8) {
self.bus.rx_keyboard(keycode);
pub fn keyboard_rx(&mut self, keycode: u8) {
self.bus.keyboard_rx(keycode);
}
pub fn mouse_move(&mut self, x: u16, y: u16) {
@ -146,9 +158,9 @@ impl Dmd {
//
#[no_mangle]
fn dmd_reset() -> c_int {
fn dmd_init(version: u8) -> c_int {
match DMD.lock() {
Ok(mut dmd) => match dmd.reset() {
Ok(mut dmd) => match dmd.reset(version) {
Ok(()) => SUCCESS,
Err(_) => ERROR,
},
@ -159,11 +171,22 @@ fn dmd_reset() -> c_int {
#[no_mangle]
fn dmd_video_ram() -> *const u8 {
match DMD.lock() {
Ok(dmd) => dmd.video_ram().as_ptr(),
Ok(mut dmd) => dmd.video_ram().as_ptr(),
Err(_) => ptr::null(),
}
}
#[no_mangle]
fn dmd_video_ram_dirty() -> c_int {
match DMD.lock() {
Ok(dmd) => match dmd.video_ram_dirty() {
true => 1,
false => 0,
},
Err(_) => 0,
}
}
#[no_mangle]
fn dmd_step() -> c_int {
match DMD.lock() {
@ -175,37 +198,6 @@ fn dmd_step() -> c_int {
}
}
/// # Safety
///
/// Uses a raw pointer.
///
#[no_mangle]
pub unsafe fn dmd_trace_on(file_name: *const c_char) -> c_int {
let file_name = CStr::from_ptr(file_name);
match DMD.lock() {
Ok(mut dmd) => match file_name.to_str() {
Ok(file_name) => match dmd.trace_on(file_name) {
Ok(()) => SUCCESS,
Err(_) => ERROR,
},
Err(_) => ERROR,
},
Err(_) => ERROR,
}
}
#[no_mangle]
pub fn dmd_trace_off() -> c_int {
match DMD.lock() {
Ok(mut dmd) => {
dmd.trace_off();
SUCCESS
}
Err(_) => ERROR,
}
}
#[no_mangle]
fn dmd_step_loop(steps: usize) -> c_int {
match DMD.lock() {
@ -278,28 +270,6 @@ fn dmd_get_duart_output_port(oport: &mut u8) -> c_int {
}
}
#[no_mangle]
fn dmd_rx_char(c: u8) -> c_int {
match DMD.lock() {
Ok(mut dmd) => {
dmd.rx_char(c as u8);
SUCCESS
}
Err(_) => ERROR,
}
}
#[no_mangle]
fn dmd_rx_keyboard(c: u8) -> c_int {
match DMD.lock() {
Ok(mut dmd) => {
dmd.rx_keyboard(c);
SUCCESS
}
Err(_) => ERROR,
}
}
#[no_mangle]
fn dmd_mouse_move(x: u16, y: u16) -> c_int {
match DMD.lock() {
@ -334,9 +304,31 @@ fn dmd_mouse_up(button: u8) -> c_int {
}
#[no_mangle]
fn dmd_rs232_tx_poll(tx_char: &mut u8) -> c_int {
fn dmd_rs232_rx(c: u8) -> c_int {
match DMD.lock() {
Ok(mut dmd) => match dmd.rs232_tx_poll() {
Ok(mut dmd) => {
dmd.rs232_rx(c as u8);
SUCCESS
}
Err(_) => ERROR,
}
}
#[no_mangle]
fn dmd_keyboard_rx(c: u8) -> c_int {
match DMD.lock() {
Ok(mut dmd) => {
dmd.keyboard_rx(c);
SUCCESS
}
Err(_) => ERROR,
}
}
#[no_mangle]
fn dmd_rs232_tx(tx_char: &mut u8) -> c_int {
match DMD.lock() {
Ok(mut dmd) => match dmd.rs232_tx() {
Some(c) => {
*tx_char = c;
SUCCESS
@ -348,9 +340,9 @@ fn dmd_rs232_tx_poll(tx_char: &mut u8) -> c_int {
}
#[no_mangle]
fn dmd_kb_tx_poll(tx_char: &mut u8) -> c_int {
fn dmd_keyboard_tx(tx_char: &mut u8) -> c_int {
match DMD.lock() {
Ok(mut dmd) => match dmd.kb_tx_poll() {
Ok(mut dmd) => match dmd.keyboard_tx() {
Some(c) => {
*tx_char = c;
SUCCESS
@ -390,7 +382,7 @@ mod tests {
#[test]
fn creates_dmd() {
let mut dmd = Dmd::new();
dmd.reset().unwrap();
dmd.reset(2).unwrap();
}
#[test]

View File

@ -1,7 +1,45 @@
use crate::bus::AccessCode;
use crate::bus::Device;
/// The 2681 DUART has two I/O UARTs, each one represented by the
/// `Port` struct. The first is used only for the EIA RS232 serial
/// port. The second is duplexed between keyboard I/O and a write-only
/// parallel printer port.
///
/// Each of the two UARTs simulates the following hardware registers:
///
/// - Two MODE registers, duplexed at the same PIO address, with a
/// pointer that auto-increments on write.
///
/// - One STATUS register.
///
/// - One CONFIGURATION register.
///
/// - One RECEIVE FIFO with 3 slots. Reading from PIO reads from this
/// FIFO.
///
/// - One RECEIVE shift register that holds bits as they come in,
/// before they are pushed onto the FIFO.
///
/// - One TRANSMIT holding register. Writing to PIO writes to this
/// register.
///
/// - One TRANSMIT shift register that latches the data from the
/// holding register and shifts them out onto the serial line.
///
/// In addition to simulating these hardware registers, this
/// implementation has a TX queue and an RX queue. These queues do not
/// exist in hardware. They are used to buffer data sent and received
/// by users of this library for maximum flexibility, since polling for
/// I/O may take place at any unknown rate. The simulated UART will
/// poll the RX queue whenever the RX FIFO is not full. It will push
/// data onto the TX queue as soon as possible after it has been moved
/// into the TX shift register.
///
/// The 2681 DUART is well documented in its datasheet.
///
use crate::bus::{AccessCode, Device};
use crate::err::BusError;
use crate::utils::FifoQueue;
use log::{debug, trace};
use std::collections::VecDeque;
use std::fmt::Debug;
use std::fmt::Error;
@ -33,12 +71,14 @@ const MR12A: u8 = 0x03;
const CSRA: u8 = 0x07;
const CRA: u8 = 0x0b;
const THRA: u8 = 0x0f;
const RHRA: u8 = 0x0f;
const IPCR_ACR: u8 = 0x13;
const ISR_MASK: u8 = 0x17;
const MR12B: u8 = 0x23;
const CSRB: u8 = 0x27;
const CRB: u8 = 0x2b;
const THRB: u8 = 0x2f;
const RHRB: u8 = 0x2f;
const IP_OPCR: u8 = 0x37;
const OPBITS_SET: u8 = 0x3b;
const OPBITS_RESET: u8 = 0x3f;
@ -46,21 +86,23 @@ const OPBITS_RESET: u8 = 0x3f;
//
// Port Configuration Bits
//
const CNF_ETX: u8 = 0x01;
const CNF_ERX: u8 = 0x02;
const CNF_ETX: u8 = 0x01; // TX Enabled
const CNF_ERX: u8 = 0x02; // RX Enabled
//
// Status Flags
//
const STS_RXR: u8 = 0x01;
const STS_TXR: u8 = 0x04;
const STS_TXE: u8 = 0x08;
const STS_OER: u8 = 0x10;
const STS_PER: u8 = 0x20;
const STS_FER: u8 = 0x40;
const STS_RXR: u8 = 0x01; // Rx Ready
const STS_FFL: u8 = 0x02; // FIFO Full
const STS_TXR: u8 = 0x04; // Tx Ready
const STS_TXE: u8 = 0x08; // Tx Register Empty
const STS_OER: u8 = 0x10; // Overflow Error
const STS_PER: u8 = 0x20; // Parity Error
const STS_FER: u8 = 0x40; // Framing Error
const STS_RXB: u8 = 0x80; // Received Break
//
// Commands
// Command Register Commands
//
const CMD_ERX: u8 = 0x01;
const CMD_DRX: u8 = 0x02;
@ -68,16 +110,29 @@ const CMD_ETX: u8 = 0x04;
const CMD_DTX: u8 = 0x08;
//
// Interrupt Status Register
// Control Register Commands
//
const ISTS_TAI: u8 = 0x01;
const ISTS_RAI: u8 = 0x02;
const ISTS_TBI: u8 = 0x10;
const ISTS_RBI: u8 = 0x20;
const ISTS_IPC: u8 = 0x80;
const CR_RST_MR: u8 = 0x01;
const CR_RST_RX: u8 = 0x02;
const CR_RST_TX: u8 = 0x03;
const CR_RST_ERR: u8 = 0x04;
const CR_RST_BRK: u8 = 0x05;
const CR_START_BRK: u8 = 0x06;
const CR_STOP_BRK: u8 = 0x07;
//
// Interrupt Masks
// Interrupt Status Register
//
const ISTS_TAI: u8 = 0x01; // Transmitter A Ready Interrupt
const ISTS_RAI: u8 = 0x02; // Receiver A Ready Interrupt
const ISTS_DBA: u8 = 0x04; // Delta Break A
const ISTS_TBI: u8 = 0x10; // Transmitter B Ready Interrupt
const ISTS_RBI: u8 = 0x20; // Receiver B Ready Interrupt
const ISTS_DBB: u8 = 0x40; // Delta Break B
const ISTS_IPC: u8 = 0x80; // Interrupt Port Change
//
// CPU Interrupt Vectors.
//
const KEYBOARD_INT: u8 = 0x04;
const MOUSE_BLANK_INT: u8 = 0x02;
@ -85,40 +140,208 @@ const TX_INT: u8 = 0x10;
const RX_INT: u8 = 0x20;
struct Port {
// Mode, Status, and Configuration registers
mode: [u8; 2],
mode_ptr: usize,
stat: u8,
conf: u8,
rx_data: u8,
tx_data: u8,
mode_ptr: usize,
rx_queue: VecDeque<u8>,
tx_queue: VecDeque<u8>,
// State used by the RX and TX state machines.
rx_fifo: FifoQueue,
rx_shift_reg: Option<u8>,
tx_holding_reg: Option<u8>,
tx_shift_reg: Option<u8>,
// Buffers to hold TX and RX characters so that they can be
// processed by the user of this library in chunks.
rx_deque: VecDeque<u8>,
tx_deque: VecDeque<u8>,
// Service timing info
char_delay: Duration,
next_rx: Instant,
next_tx: Instant,
next_tx_service: Instant,
next_rx_service: Instant,
}
impl Port {
fn new() -> Port {
Port {
mode: [0; 2],
mode_ptr: 0,
stat: 0,
conf: 0,
rx_data: 0,
tx_data: 0,
mode_ptr: 0,
rx_queue: VecDeque::new(),
tx_queue: VecDeque::new(),
rx_fifo: FifoQueue::new(),
rx_shift_reg: None,
tx_holding_reg: None,
tx_shift_reg: None,
rx_deque: VecDeque::new(),
tx_deque: VecDeque::new(),
char_delay: Duration::new(0, 1_000_000),
next_rx: Instant::now(),
next_tx: Instant::now(),
next_tx_service: Instant::now(),
next_rx_service: Instant::now(),
}
}
}
impl Default for Port {
fn default() -> Self {
Port::new()
/// Read a single character out of the RX channel.
///
/// The character is read out of the FIFO, which is drained of one
/// slot. If a character is currently in the shift register, it is
/// moved into the FIFO now that there is room.
fn rx_read_char(&mut self) -> Option<u8> {
if !self.rx_enabled() {
return None;
}
if let Ok(val) = self.rx_fifo.pop() {
// The FIFO is no longer full (even if only temporarily)
self.stat &= !STS_FFL;
// If the RX shift register held a character, it's time
// to move it into the FIFO.
if let Some(c) = self.rx_shift_reg {
let _ = self.rx_fifo.push(c);
self.stat |= STS_RXR;
if self.rx_fifo.is_full() {
self.stat |= STS_FFL;
}
}
// If the FIFO is now empty, mark it as such.
if self.rx_fifo.is_empty() {
self.stat &= !STS_RXR;
}
// Reset Parity Error and Received Break on read
self.stat &= !(STS_PER | STS_RXB);
Some(val)
} else {
None
}
}
/// Receiver a single character.
///
/// If there is room in the FIFO, the character is immediately
/// stored there. If not, it is stored in the shift register until
/// room becomes available. The shift register is overwitten if a
/// new character is received while the FIFO is full.
fn rx_char(&mut self, c: u8) {
trace!("rx_char: {:02x}, fifo_len={}", c, self.rx_fifo.len());
if !self.rx_fifo.is_full() {
let _ = self.rx_fifo.push(c);
if self.rx_fifo.is_full() {
trace!("FIFO FULL.");
self.stat |= STS_FFL;
}
} else {
// The FIFO is full, and now we're going to have to
// hold a character in the shift register until space
// is available.
//
// If the register already had data, it's going to be
// overwritten, so we have to set the overflow flag.
if self.rx_shift_reg.is_some() {
self.stat |= STS_OER;
}
self.rx_shift_reg = Some(c);
}
// In either case, RxRDY is now true.
self.stat |= STS_RXR;
}
/// Move the receiver state machine
fn rx_service(&mut self) {
let rx_service_needed = self.rx_enabled()
&& !self.rx_deque.is_empty()
&& Instant::now() >= self.next_rx_service;
if !rx_service_needed {
// Nothing to do.
return;
}
if !self.loopback() {
if let Some(c) = self.rx_deque.pop_back() {
self.rx_char(c);
}
}
self.next_rx_service = Instant::now() + self.char_delay;
}
/// Move the transmitter state machine.
fn tx_service(&mut self, keyboard: bool) {
if self.tx_holding_reg.is_none() && self.tx_shift_reg.is_none() {
// Nothing to do
return;
}
if Instant::now() >= self.next_tx_service {
// Check for data in the transmitter shift register that's
// ready to go out to the RS232 output buffer
if let Some(c) = self.tx_shift_reg {
trace!("RS232 TX: {:02x}", c);
if self.loopback() {
debug!("RS232 TX: LOOPBACK: Finish transmit character {:02x}", c);
self.rx_char(c);
} else {
if keyboard && c == 0x02 {
self.stat |= STS_PER;
}
debug!("RS232 TX: Finish transmit character {:02x}", c);
self.tx_deque.push_front(c);
}
self.tx_shift_reg = None;
if self.tx_holding_reg.is_none() {
self.stat |= STS_TXR;
self.stat |= STS_TXE;
}
}
// Check for data in the holding register that's ready to go
// out to the shift register.
if let Some(c) = self.tx_holding_reg {
self.tx_shift_reg = Some(c);
// Clear the holding register
self.tx_holding_reg = None;
// Ready for a new character
self.next_tx_service = Instant::now() + self.char_delay;
}
}
}
fn loopback(&self) -> bool {
(self.mode[1] & 0xc0) == 0x80
}
fn rx_enabled(&self) -> bool {
(self.conf & CNF_ERX) != 0
}
fn enable_tx(&mut self) {
self.conf |= CNF_ETX;
if self.tx_holding_reg.is_none() {
self.stat |= STS_TXR;
}
if self.tx_shift_reg.is_none() {
self.stat |= STS_TXE;
}
}
fn disable_tx(&mut self) {
self.conf &= !CNF_ETX;
self.stat &= !(STS_TXR | STS_TXE);
}
fn enable_rx(&mut self) {
self.conf |= CNF_ERX;
self.stat &= !STS_RXR;
}
fn disable_rx(&mut self) {
self.conf &= !CNF_ERX;
self.stat &= !STS_RXR;
}
}
@ -128,8 +351,11 @@ pub struct Duart {
ipcr: u8,
inprt: u8,
outprt: u8,
istat: u8,
isr: u8,
imr: u8,
// TODO: Interrupts are set manually by tweaking this vector,
// which doesn't actually exist on the real duart. We should fix
// that, because DAMN.
ivec: u8,
next_vblank: Instant,
}
@ -141,9 +367,9 @@ impl Default for Duart {
}
/// Compute the delay rate to wait for the next transmit or receive
fn delay_rate(csr_bits: u8, acr_bits: u8) -> u32 {
const NS_PER_SEC: u32 = 1_100_000_000;
const BITS_PER_CHAR: u32 = 7;
fn delay_rate(csr_bits: u8, acr_bits: u8) -> Duration {
const NS_PER_SEC: u32 = 1_000_000_000;
const BITS_PER_CHAR: u32 = 8;
let baud_bits: usize = ((csr_bits >> 4) & 0xf) as usize;
let baud_rate = if acr_bits & 0x80 == 0 {
@ -152,7 +378,7 @@ fn delay_rate(csr_bits: u8, acr_bits: u8) -> u32 {
BAUD_RATES_B[baud_bits]
};
NS_PER_SEC / (baud_rate / BITS_PER_CHAR)
Duration::new(0, NS_PER_SEC / (baud_rate / BITS_PER_CHAR))
}
impl Duart {
@ -163,7 +389,7 @@ impl Duart {
ipcr: 0x40,
inprt: 0xb,
outprt: 0,
istat: 0,
isr: 0,
imr: 0,
ivec: 0,
next_vblank: Instant::now() + Duration::new(0, VERTICAL_BLANK_DELAY),
@ -176,6 +402,22 @@ impl Duart {
self.vertical_blank();
}
// Mask in keyboard and RS232 RX interrupts
if (self.ports[PORT_0].stat & STS_RXR) != 0 {
self.ivec |= RX_INT;
self.isr |= ISTS_RAI;
}
if (self.ports[PORT_1].stat & STS_RXR) != 0 {
self.ivec |= KEYBOARD_INT;
self.isr |= ISTS_RBI;
}
if (self.ports[PORT_0].stat & STS_TXR) != 0 {
self.ivec |= TX_INT;
self.isr |= ISTS_TAI;
}
let val = self.ivec;
if val == 0 {
@ -185,82 +427,17 @@ impl Duart {
}
}
fn handle_rx(&mut self, port: usize) {
let mut ctx = &mut self.ports[port];
let (istat, ivec) = match port {
0 => (ISTS_RAI, RX_INT),
_ => (ISTS_RBI, KEYBOARD_INT),
};
if !ctx.rx_queue.is_empty() && Instant::now() >= ctx.next_rx {
if let Some(c) = ctx.rx_queue.pop_back() {
if ctx.conf & CNF_ERX != 0 {
ctx.rx_data = c;
ctx.stat |= STS_RXR;
self.istat |= istat;
self.ivec |= ivec;
}
}
if !ctx.rx_queue.is_empty() {
ctx.next_rx = Instant::now() + ctx.char_delay;
}
}
}
fn handle_tx(&mut self, port: usize) {
let mut ctx = &mut self.ports[port];
let (tx_istat, rx_istat) = match port {
0 => (ISTS_TAI, ISTS_RAI),
_ => (ISTS_TBI, ISTS_RBI),
};
if (ctx.conf & CNF_ETX) != 0
&& (ctx.stat & STS_TXR) == 0
&& (ctx.stat & STS_TXE) == 0
&& Instant::now() >= ctx.next_tx
{
let c = ctx.tx_data;
ctx.stat |= STS_TXR;
ctx.stat |= STS_TXE;
self.istat |= tx_istat;
// Only RS232 transmit generates an interrupt.
if port == 0 {
self.ivec |= TX_INT;
}
if (ctx.mode[1] >> 6) & 3 == 0x2 {
// Loopback Mode.
ctx.rx_data = c;
ctx.stat |= STS_RXR;
self.istat |= rx_istat;
self.ivec |= RX_INT;
} else {
ctx.tx_queue.push_front(c);
}
}
}
pub fn service(&mut self) {
// Deal with RS-232 Transmit
self.handle_tx(PORT_0);
// Deal with RS-232 Receive
self.handle_rx(PORT_0);
// Deal with Keyboard Transmit
// (note: This is used only for keyboard beeps!)
self.handle_tx(PORT_1);
// Deal with Keyboard Receive
self.handle_rx(PORT_1);
self.ports[PORT_0].tx_service(false);
self.ports[PORT_0].rx_service();
self.ports[PORT_1].tx_service(true);
self.ports[PORT_1].rx_service();
}
pub fn vertical_blank(&mut self) {
self.ivec |= MOUSE_BLANK_INT;
self.ipcr |= 0x40;
self.istat |= ISTS_IPC;
self.isr |= ISTS_IPC;
if self.inprt & 0x04 == 0 {
self.ipcr |= 0x40;
@ -271,21 +448,32 @@ impl Duart {
pub fn output_port(&self) -> u8 {
// The output port always returns a complement of the bits
debug!("READ: Output Port: {:02x}", !self.outprt);
!self.outprt
}
pub fn rs232_tx_poll(&mut self) -> Option<u8> {
self.ports[PORT_0].tx_queue.pop_back()
/// Queue a single character for processing by the rs232 port.
pub fn rs232_rx(&mut self, c: u8) {
self.ports[PORT_0].rx_deque.push_front(c);
}
pub fn kb_tx_poll(&mut self) -> Option<u8> {
self.ports[PORT_1].tx_queue.pop_back()
/// Queue a single character for processing by the keyboard port
pub fn keyboard_rx(&mut self, c: u8) {
self.ports[PORT_1].rx_deque.push_front(c);
}
pub fn rs232_tx(&mut self) -> Option<u8> {
self.ports[PORT_0].tx_deque.pop_back()
}
pub fn keyboard_tx(&mut self) -> Option<u8> {
self.ports[PORT_1].tx_deque.pop_back()
}
pub fn mouse_down(&mut self, button: u8) {
self.ipcr = 0;
self.inprt |= 0xb;
self.istat |= ISTS_IPC;
self.isr |= ISTS_IPC;
self.ivec |= MOUSE_BLANK_INT;
match button {
0 => {
@ -307,7 +495,7 @@ impl Duart {
pub fn mouse_up(&mut self, button: u8) {
self.ipcr = 0;
self.inprt |= 0xb;
self.istat |= ISTS_IPC;
self.isr |= ISTS_IPC;
self.ivec |= MOUSE_BLANK_INT;
match button {
0 => {
@ -323,81 +511,104 @@ impl Duart {
}
}
pub fn rx_keyboard(&mut self, c: u8) {
let mut ctx = &mut self.ports[PORT_1];
fn handle_command(&mut self, cmd: u8, port_no: usize) {
let mut port = &mut self.ports[port_no];
if ctx.rx_queue.is_empty() {
ctx.next_rx = Instant::now() + ctx.char_delay;
}
let (tx_ists, rx_ists, dbk_ists) = match port_no {
PORT_0 => (ISTS_TAI, ISTS_RAI, ISTS_DBA),
_ => (ISTS_TBI, ISTS_RBI, ISTS_DBB),
};
ctx.rx_queue.push_front(c);
}
pub fn rx_char(&mut self, c: u8) {
let mut ctx = &mut self.ports[PORT_0];
if ctx.rx_queue.is_empty() {
ctx.next_rx = Instant::now() + ctx.char_delay;
}
ctx.rx_queue.push_front(c);
}
pub fn handle_command(&mut self, cmd: u8, port: usize) {
if cmd == 0 {
return;
}
let mut ctx = &mut self.ports[port];
// Enable or disable transmitter
// Enable or disable transmitter. Disable always wins if both
// are set.
if cmd & CMD_DTX != 0 {
ctx.conf &= !CNF_ETX;
ctx.stat &= !STS_TXR;
ctx.stat &= !STS_TXE;
if port == PORT_0 {
self.ivec &= !TX_INT;
self.istat &= !ISTS_TAI;
}
debug!("Command: Disable TX");
port.disable_tx();
self.ivec &= !TX_INT; // XXX: Rethink interrupt vector setting.
self.isr &= !tx_ists;
} else if cmd & CMD_ETX != 0 {
ctx.conf |= CNF_ETX;
ctx.stat |= STS_TXR;
ctx.stat |= STS_TXE;
if port == PORT_0 {
self.istat |= ISTS_TAI;
self.ivec |= TX_INT;
}
debug!("Command: Enable TX");
port.enable_tx();
self.ivec |= TX_INT; // XXX: Rethink interrupt vector setting
self.isr |= tx_ists;
}
// Enable or disable receiver
// Enable or disable receiver. Disable always wins, if both are set.
if cmd & CMD_DRX != 0 {
ctx.conf &= !CNF_ERX;
ctx.stat &= !STS_RXR;
if port == PORT_0 {
self.ivec &= !RX_INT;
self.istat &= !ISTS_RAI;
debug!("Command: Disable RX");
port.disable_rx();
self.isr &= !rx_ists;
if port_no == PORT_0 {
self.ivec &= !RX_INT; // XXX: Rethink interrupt vector setting
self.isr &= !ISTS_RAI;
} else {
self.ivec &= !KEYBOARD_INT;
self.istat &= !ISTS_RBI;
self.ivec &= !KEYBOARD_INT; // XXX: Rethink interrupt vector setting
self.isr &= !ISTS_RBI;
}
} else if cmd & CMD_ERX != 0 {
ctx.conf |= CNF_ERX;
ctx.stat |= STS_RXR;
debug!("Command: Enable RX");
port.enable_rx();
}
// Extra commands
match (cmd >> 4) & 7 {
1 => ctx.mode_ptr = 0,
2 => {
ctx.stat |= STS_RXR;
ctx.conf |= CNF_ERX;
CR_RST_MR => {
debug!("PORT{}: Reset MR Pointer.", port_no);
port.mode_ptr = 0
}
3 => {
ctx.stat |= STS_TXR;
ctx.stat |= STS_TXE;
ctx.conf &= !CNF_ETX;
CR_RST_RX => {
// Reset the channel's receiver as if a hardware reset
// had been performed. The receiver is disabled and
// the FIFO is flushed.
debug!("PORT{}: Reset RX.", port_no);
port.stat &= !STS_RXR;
port.conf &= !CNF_ERX;
port.rx_fifo.clear();
port.rx_shift_reg = None;
}
CR_RST_TX => {
// Reset the channel's transmitter as if a hardware reset
// had been performed.
debug!("PORT{}: Reset TX.", port_no);
port.stat &= !(STS_TXR | STS_TXE);
port.conf &= !CNF_ETX;
port.tx_holding_reg = None;
port.tx_shift_reg = None;
}
CR_RST_ERR => {
debug!("PORT{}: Reset Error.", port_no);
port.stat &= !(STS_RXB | STS_FER | STS_PER | STS_OER);
}
CR_RST_BRK => {
// Reset the channel's Delta Break interrupt. Causes
// the channel's break detect change bit in the
// interrupt status register to be cleared to 0.
debug!("PORT{}: Reset Break Interrupt.", port_no);
self.isr &= !dbk_ists;
}
CR_START_BRK => {
debug!("PORT{}: Start Break.", port_no);
if port.loopback() {
// We only care about a BREAK condition if it's
// looping back to te receiver.
//
// TODO: We may want to expose a BREAK condition
// to the outside world at some point.
port.stat |= STS_RXB | STS_PER;
// Set "Delta Break"
self.isr |= dbk_ists;
}
}
CR_STOP_BRK => {
debug!("PORT{}: Stop Break.", port_no);
if port.loopback() {
// We only care about a BREAK condition if it's
// looping back to te receiver.
//
// Set "Delta Break"
self.isr |= dbk_ists;
}
}
4 => ctx.stat &= !(STS_FER | STS_PER | STS_OER),
_ => {}
}
}
@ -428,40 +639,72 @@ impl Device for Duart {
let mut ctx = &mut self.ports[PORT_0];
let val = ctx.mode[ctx.mode_ptr];
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
trace!("READ : MR12A, val={:02x}", val);
Ok(val)
}
CSRA => Ok(self.ports[PORT_0].stat),
THRA => {
let mut ctx = &mut self.ports[PORT_0];
ctx.stat &= !STS_RXR;
self.istat &= !ISTS_RAI;
CSRA => {
let val = self.ports[PORT_0].stat;
trace!("READ : CSRA, val={:02x}", val);
Ok(val)
}
RHRA => {
let ctx = &mut self.ports[PORT_0];
self.isr &= !ISTS_RAI;
self.ivec &= !RX_INT;
Ok(ctx.rx_data)
let val = if let Some(c) = ctx.rx_read_char() {
c
} else {
0
};
debug!("READ : RHRA, val={:02x}", val);
Ok(val)
}
IPCR_ACR => {
let result = self.ipcr;
let val = self.ipcr;
self.ipcr &= !0x0f;
self.ivec = 0;
self.istat &= !ISTS_IPC;
Ok(result)
self.isr &= !ISTS_IPC;
trace!("READ : IPCR_ACR, val={:02x}", val);
Ok(val)
}
ISR_MASK => {
let val = self.isr;
trace!("READ : ISR_MASK, val={:02x}", val);
Ok(val)
}
ISR_MASK => Ok(self.istat),
MR12B => {
let mut ctx = &mut self.ports[PORT_1];
let val = ctx.mode[ctx.mode_ptr];
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
trace!("READ : MR12B, val={:02x}", val);
Ok(val)
}
CSRB => Ok(self.ports[PORT_1].stat),
THRB => {
let mut ctx = &mut self.ports[PORT_1];
ctx.stat &= !STS_RXR;
self.istat &= !ISTS_RBI;
self.ivec &= !KEYBOARD_INT;
Ok(ctx.rx_data)
CSRB => {
let val = self.ports[PORT_1].stat;
trace!("READ : CSRB, val={:02x}", val);
Ok(val)
}
RHRB => {
let ctx = &mut self.ports[PORT_1];
self.isr &= !ISTS_RAI;
self.ivec &= !KEYBOARD_INT;
let val = if let Some(c) = ctx.rx_read_char() {
c
} else {
0
};
debug!("READ : RHRB, val={:02x}", val);
Ok(val)
}
IP_OPCR => {
let val = self.inprt;
trace!("READ : IP_OPCR val={:02x}", val);
Ok(val)
}
_ => {
trace!("READ : UNHANDLED. ADDRESS={:08x}", address);
Err(BusError::NoDevice(address))
}
IP_OPCR => Ok(self.inprt),
_ => Ok(0),
}
}
@ -478,70 +721,77 @@ impl Device for Duart {
fn write_byte(&mut self, address: usize, val: u8, _access: AccessCode) -> Result<(), BusError> {
match (address - START_ADDR) as u8 {
MR12A => {
trace!("WRITE: MR12A, val={:02x}", val);
let mut ctx = &mut self.ports[PORT_0];
ctx.mode[ctx.mode_ptr] = val;
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
}
CSRA => {
trace!("WRITE: CSRA, val={:02x}", val);
let mut ctx = &mut self.ports[PORT_0];
ctx.char_delay = Duration::new(0, delay_rate(val, self.acr));
ctx.char_delay = delay_rate(val, self.acr);
}
CRA => {
trace!("WRITE: CRA, val={:02x}", val);
self.handle_command(val, PORT_0);
}
THRA => {
let mut ctx = &mut self.ports[PORT_0];
ctx.tx_data = val;
// Update state. Since we're transmitting, the
// transmitter buffer is not empty. The actual
// transmit will happen in the 'service' function.
ctx.next_tx = Instant::now() + ctx.char_delay;
ctx.stat &= !(STS_TXE | STS_TXR);
self.istat &= !ISTS_TAI;
self.ivec &= !TX_INT;
debug!("WRITE: THRA, val={:02x}", val);
ctx.tx_holding_reg = Some(val);
// TxRDY and TxEMT are both de-asserted on load.
ctx.stat &= !(STS_TXR | STS_TXE);
self.isr &= !ISTS_TAI;
}
IPCR_ACR => {
trace!("WRITE: IPCR_ACR, val={:02x}", val);
self.acr = val;
// TODO: Update baud rate generator
}
ISR_MASK => {
trace!("WRITE: ISR_MASK, val={:02x}", val);
self.imr = val;
}
MR12B => {
trace!("WRITE: MR12B, val={:02x}", val);
let mut ctx = &mut self.ports[PORT_1];
ctx.mode[ctx.mode_ptr] = val;
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
}
CSRB => {
trace!("WRITE: CSRB, val={:02x}", val);
let mut ctx = &mut self.ports[PORT_1];
ctx.char_delay = delay_rate(val, self.acr);
}
CRB => {
trace!("WRITE: CRB, val={:02x}", val);
self.handle_command(val, PORT_1);
}
THRB => {
debug!("WRITE: THRB, val={:02x}", val);
// TODO: When OP3 is low, do not send data to
// the keyboard! It's meant for the printer.
// Keyboard transmit requires special handling,
// because the only things the terminal transmits to
// the keyboard are status requests, or keyboard beep
// requests. We ignore status requests, and only
// put beep requests into the queue.
let mut ctx = &mut self.ports[PORT_1];
if (val & 0x08) != 0 {
ctx.tx_data = val;
ctx.next_tx = Instant::now() + ctx.char_delay;
ctx.stat &= !(STS_TXE | STS_TXR);
self.istat &= !ISTS_TBI;
}
ctx.tx_holding_reg = Some(val);
// TxRDY and TxEMT are both de-asserted on load.
ctx.stat &= !(STS_TXR | STS_TXE);
self.isr &= !ISTS_TBI;
}
IP_OPCR => {
trace!("WRITE: IP_OPCR, val={:02x}", val);
// Not implemented
}
OPBITS_SET => {
trace!("WRITE: OPBITS_SET, val={:02x}", val);
self.outprt |= val;
}
OPBITS_RESET => {
trace!("WRITE: OPBITS_RESET, val={:02x}", val);
self.outprt &= !val;
}
_ => {}
_ => {
trace!("WRITE: UNHANDLED. ADDRESS={:08x}", address);
}
};
Ok(())

View File

@ -1,14 +1,21 @@
pub mod bus;
pub mod cpu;
pub mod dmd;
pub mod duart;
pub mod err;
pub mod instr;
pub mod mem;
pub mod mouse;
pub mod rom_hi;
pub mod rom_lo;
#[allow(unused)]
mod bus;
#[allow(unused)]
mod cpu;
#[allow(unused)]
mod dmd;
mod duart;
#[allow(unused)]
mod err;
#[allow(unused)]
mod instr;
mod mem;
mod mouse;
mod rom_hi;
mod rom_lo;
mod utils;
#[macro_use]
extern crate lazy_static;
extern crate libc;
extern crate log;

View File

@ -5,6 +5,8 @@ use crate::bus::Device;
use crate::err::BusError;
use std::ops::Range;
use log::trace;
const START_ADDRESS: usize = 0x400000;
const END_ADDRESS: usize = 0x4000004;
const ADDRESS_RANGE: Range<usize> = START_ADDRESS..END_ADDRESS;
@ -48,6 +50,7 @@ impl Device for Mouse {
}
fn read_half(&mut self, address: usize, _access: AccessCode) -> Result<u16, BusError> {
trace!("Mouse Read, address={:08x}", address);
match address - START_ADDRESS {
0 => Ok(self.y),
2 => Ok(self.x),

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

116
src/utils.rs Normal file
View File

@ -0,0 +1,116 @@
use thiserror::Error;
const FIFO_LEN: usize = 3;
/// A simple circular buffer with three slots, used as a
/// DUART character FIFO
pub struct FifoQueue {
buf: [u8; 3],
read_ptr: usize,
write_ptr: usize,
len: usize,
}
#[derive(Error, Debug, Eq, PartialEq)]
pub enum FifoError {
#[error("fifo full")]
Overflow,
#[error("fifo under-run")]
Underrun,
}
impl FifoQueue {
pub fn new() -> Self {
FifoQueue {
buf: [0; FIFO_LEN],
read_ptr: 0,
write_ptr: 0,
len: 0,
}
}
pub fn push(&mut self, c: u8) -> Result<(), FifoError> {
if self.len == FIFO_LEN {
Err(FifoError::Overflow)
} else {
self.buf[self.write_ptr] = c;
self.write_ptr = (self.write_ptr + 1) % FIFO_LEN;
self.len += 1;
Ok(())
}
}
pub fn pop(&mut self) -> Result<u8, FifoError> {
if self.len == 0 {
Err(FifoError::Underrun)
} else {
let c = self.buf[self.read_ptr];
self.read_ptr = (self.read_ptr + 1) % FIFO_LEN;
self.len -= 1;
Ok(c)
}
}
pub fn clear(&mut self) {
self.read_ptr = 0;
self.write_ptr = 0;
self.len = 0;
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn is_full(&self) -> bool {
self.len == FIFO_LEN
}
pub fn len(&self) -> usize {
self.len
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn pops_in_order() {
let mut f: FifoQueue = FifoQueue::new();
assert_eq!(Ok(()), f.push(1));
assert_eq!(Ok(()), f.push(2));
assert_eq!(Ok(()), f.push(3));
assert_eq!(Ok(1), f.pop());
assert_eq!(Ok(2), f.pop());
assert_eq!(Ok(3), f.pop());
}
#[test]
fn popping_when_empty_returns_underrun_error() {
let mut f: FifoQueue = FifoQueue::new();
assert_eq!(0, f.len());
assert_eq!(Err(FifoError::Underrun), f.pop());
assert_eq!(Ok(()), f.push(42));
assert_eq!(1, f.len());
assert_eq!(Ok(42), f.pop());
assert_eq!(Err(FifoError::Underrun), f.pop());
}
#[test]
fn pushing_when_full_returns_overflow_error() {
let mut f: FifoQueue = FifoQueue::new();
assert_eq!(0, f.len());
assert_eq!(Ok(()), f.push(0xff));
assert_eq!(1, f.len());
assert_eq!(Ok(()), f.push(0xfe));
assert_eq!(2, f.len());
assert_eq!(Ok(()), f.push(0xfd));
assert_eq!(3, f.len());
assert_eq!(Err(FifoError::Overflow), f.push(0xfc));
assert_eq!(3, f.len());
}
}