Updated TX and RX polling
This commit is contained in:
parent
badd42c2b6
commit
4816d3cdc0
12
README.md
12
README.md
|
@ -19,7 +19,11 @@ terminal, including:
|
|||
- 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.
|
||||
Note that there is no user interface: This is a back-end library only.
|
||||
It may be used as a component to build a fully-fledged emulator,
|
||||
however.
|
||||
|
||||
## Emulator Reference Implementation
|
||||
|
||||
For a reference implementation emulator that uses this library,
|
||||
please see the ["DMD" project on GitHub](https://github.com/sethm/dmd).
|
||||
|
|
20
src/bus.rs
20
src/bus.rs
|
@ -64,10 +64,10 @@ pub struct Bus {
|
|||
}
|
||||
|
||||
impl Bus {
|
||||
pub fn new<CB: 'static + FnMut(u8) + Send + Sync>(mem_size: usize, tx_callback: CB) -> Bus {
|
||||
pub fn new(mem_size: usize) -> Bus {
|
||||
Bus {
|
||||
rom: Mem::new(0, 0x20000, true),
|
||||
duart: Duart::new(tx_callback),
|
||||
duart: Duart::new(),
|
||||
scc: Mem::new(0x300000, 0x100, false),
|
||||
mouse: Mouse::new(),
|
||||
vid: Mem::new(0x500000, 0x2, false),
|
||||
|
@ -176,10 +176,6 @@ impl Bus {
|
|||
self.duart.get_interrupt()
|
||||
}
|
||||
|
||||
pub fn keyboard(&mut self, keycode: u8) {
|
||||
self.duart.handle_keyboard(keycode);
|
||||
}
|
||||
|
||||
pub fn mouse_move(&mut self, x: u16, y: u16) {
|
||||
self.mouse.x = x;
|
||||
self.mouse.y = y;
|
||||
|
@ -193,10 +189,18 @@ impl Bus {
|
|||
self.duart.mouse_up(button);
|
||||
}
|
||||
|
||||
pub fn tx_poll(&mut self) -> Option<u8> {
|
||||
self.duart.tx_poll()
|
||||
}
|
||||
|
||||
pub fn rx_char(&mut self, char: u8) -> Result<(), DuartError> {
|
||||
self.duart.rx_char(char)
|
||||
}
|
||||
|
||||
pub fn rx_keyboard(&mut self, keycode: u8) -> Result<(), DuartError> {
|
||||
self.duart.rx_keyboard(keycode)
|
||||
}
|
||||
|
||||
pub fn rx_ready(&self) -> bool {
|
||||
self.duart.rx_ready()
|
||||
}
|
||||
|
@ -206,11 +210,9 @@ impl Bus {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn tx_callback(_char: u8) {}
|
||||
|
||||
#[test]
|
||||
fn should_fail_on_alignment_errors() {
|
||||
let mut bus: Bus = Bus::new(0x10000, tx_callback);
|
||||
let mut bus: Bus = Bus::new(0x10000);
|
||||
|
||||
assert!(bus.write_byte(0x700000, 0x1f).is_ok());
|
||||
assert!(bus.write_half(0x700000, 0x1f1f).is_ok());
|
||||
|
|
|
@ -2104,8 +2104,6 @@ mod tests {
|
|||
|
||||
const BASE: usize = 0x700000;
|
||||
|
||||
fn tx_callback(_char: u8) {}
|
||||
|
||||
/// Helper function to set up and prepare a cpu and bus
|
||||
/// with a supplied program.
|
||||
fn do_with_program<F>(program: &[u8], test: F)
|
||||
|
@ -2113,7 +2111,7 @@ mod tests {
|
|||
F: Fn(&mut Cpu, &mut Bus),
|
||||
{
|
||||
let mut cpu: Cpu = Cpu::new();
|
||||
let mut bus: Bus = Bus::new(0x10000, tx_callback);
|
||||
let mut bus: Bus = Bus::new(0x10000);
|
||||
|
||||
bus.load(BASE, &program).unwrap();
|
||||
cpu.r[R_PC] = BASE as u32;
|
||||
|
|
82
src/dmd.rs
82
src/dmd.rs
|
@ -12,15 +12,21 @@ pub struct Dmd {
|
|||
}
|
||||
|
||||
impl Dmd {
|
||||
pub fn new<CB: 'static + FnMut(u8) + Send + Sync>(tx_callback: CB) -> Dmd {
|
||||
///
|
||||
/// Construct a new DMD Terminal
|
||||
///
|
||||
pub fn new() -> Dmd {
|
||||
let cpu = Cpu::new();
|
||||
let bus = Bus::new(0x100000, tx_callback);
|
||||
let bus = Bus::new(0x100000);
|
||||
Dmd {
|
||||
cpu,
|
||||
bus,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Reset the terminal's CPU. This is equivalent to a hard power reset.
|
||||
///
|
||||
pub fn reset(&mut self) -> Result<(), BusError> {
|
||||
self.bus.load(0, &LO_ROM)?;
|
||||
self.bus.load(0x10000, &HI_ROM)?;
|
||||
|
@ -29,30 +35,44 @@ impl Dmd {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Return a view of the terminal's Video Memory
|
||||
///
|
||||
pub fn video_ram(&self) -> Result<&[u8], BusError> {
|
||||
self.bus.video_ram()
|
||||
}
|
||||
|
||||
pub fn step(&mut self) {
|
||||
self.cpu.step(&mut self.bus);
|
||||
}
|
||||
|
||||
///
|
||||
/// Return the contents of the CPU's Program Counter.
|
||||
///
|
||||
pub fn get_pc(&self) -> u32 {
|
||||
self.cpu.get_pc()
|
||||
}
|
||||
|
||||
///
|
||||
/// Return the contents of the CPU's Argument Pointer.
|
||||
///
|
||||
pub fn get_ap(&self) -> u32 {
|
||||
self.cpu.get_ap()
|
||||
}
|
||||
|
||||
///
|
||||
/// Return the contents of the CPU's Processor Status Word.
|
||||
///
|
||||
pub fn get_psw(&self) -> u32 {
|
||||
self.cpu.get_psw()
|
||||
}
|
||||
|
||||
///
|
||||
/// Return the contents of a CPU register (0-15)
|
||||
///
|
||||
pub fn get_register(&self, reg: u8) -> u32 {
|
||||
self.cpu.r[reg as usize]
|
||||
self.cpu.r[(reg & 0xf) as usize]
|
||||
}
|
||||
|
||||
///
|
||||
/// Read a word from the terminal's memory. NB: Address must be word aligned.
|
||||
///
|
||||
pub fn read(&mut self, addr: usize) -> Option<u32> {
|
||||
match self.bus.read_word(addr, AccessCode::AddressFetch) {
|
||||
Ok(d) => Some(d),
|
||||
|
@ -60,30 +80,66 @@ impl Dmd {
|
|||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Step the terminal's CPU one time, allowing the CPU's internal error and
|
||||
/// exception handling to take care of all error / exception types.
|
||||
///
|
||||
pub fn step(&mut self) {
|
||||
self.cpu.step(&mut self.bus);
|
||||
}
|
||||
|
||||
///
|
||||
/// Step the terminal's CPU one time, returning any error that may have occured.
|
||||
/// Useful for debugging.
|
||||
///
|
||||
pub fn step_with_error(&mut self) -> Result<(), CpuError> {
|
||||
self.cpu.step_with_error(&mut self.bus)
|
||||
}
|
||||
|
||||
///
|
||||
/// Poll for a character to transmit from the terminal to the host.
|
||||
///
|
||||
pub fn tx_poll(&mut self) -> Option<u8> {
|
||||
self.bus.tx_poll()
|
||||
}
|
||||
|
||||
///
|
||||
/// Receive a character from the host to the terminal.
|
||||
///
|
||||
pub fn rx_char(&mut self, character: u8) -> Result<(), DuartError> {
|
||||
self.bus.rx_char(character)
|
||||
}
|
||||
|
||||
///
|
||||
/// Receive a character from the keyboard to the terminal.
|
||||
///
|
||||
pub fn rx_keyboard(&mut self, keycode: u8) -> Result<(), DuartError> {
|
||||
self.bus.rx_keyboard(keycode)
|
||||
}
|
||||
|
||||
///
|
||||
/// Check to see if the terminal is ready to receive a new character.
|
||||
///
|
||||
pub fn rx_ready(&self) -> bool {
|
||||
self.bus.rx_ready()
|
||||
}
|
||||
|
||||
pub fn keyboard(&mut self, keycode: u8) {
|
||||
self.bus.keyboard(keycode);
|
||||
}
|
||||
|
||||
///
|
||||
/// Register a new mouse position with the terminal.
|
||||
///
|
||||
pub fn mouse_move(&mut self, x: u16, y: u16) {
|
||||
self.bus.mouse_move(x, y);
|
||||
}
|
||||
|
||||
///
|
||||
/// Register a "mouse down" event with the terminal.
|
||||
pub fn mouse_down(&mut self, button: u8) {
|
||||
self.bus.mouse_down(button);
|
||||
}
|
||||
|
||||
///
|
||||
/// Register a "mouse up" event with the terminal.
|
||||
///
|
||||
pub fn mouse_up(&mut self, button: u8) {
|
||||
self.bus.mouse_up(button);
|
||||
}
|
||||
|
@ -93,11 +149,9 @@ impl Dmd {
|
|||
mod tests {
|
||||
use crate::dmd::Dmd;
|
||||
|
||||
fn tx_callback(_char: u8) {}
|
||||
|
||||
#[test]
|
||||
fn creates_dmd() {
|
||||
let mut dmd = Dmd::new(tx_callback);
|
||||
let mut dmd = Dmd::new();
|
||||
dmd.reset().unwrap();
|
||||
}
|
||||
}
|
||||
|
|
52
src/duart.rs
52
src/duart.rs
|
@ -1,13 +1,15 @@
|
|||
use crate::bus::AccessCode;
|
||||
use crate::bus::Device;
|
||||
use crate::err::BusError;
|
||||
use crate::err::DuartError;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Error;
|
||||
use std::fmt::Formatter;
|
||||
use std::ops::Range;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use crate::err::DuartError;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
const START_ADDR: usize = 0x200000;
|
||||
const END_ADDR: usize = 0x2000040;
|
||||
|
@ -112,11 +114,11 @@ pub struct Duart {
|
|||
imr: u8,
|
||||
ivec: u8,
|
||||
last_vblank: Instant,
|
||||
tx_callback: Option<Box<FnMut(u8) + Send + Sync>>,
|
||||
tx_queue: VecDeque<u8>,
|
||||
}
|
||||
|
||||
impl Duart {
|
||||
pub fn new<CB: 'static + FnMut(u8) + Send + Sync>(tx_callback: CB) -> Duart {
|
||||
pub fn new() -> Duart {
|
||||
Duart {
|
||||
ports: [
|
||||
Port {
|
||||
|
@ -153,7 +155,7 @@ impl Duart {
|
|||
imr: 0,
|
||||
ivec: 0,
|
||||
last_vblank: Instant::now(),
|
||||
tx_callback: Some(Box::new(tx_callback)),
|
||||
tx_queue: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,22 +195,11 @@ impl Duart {
|
|||
self.istat |= ISTS_RAI;
|
||||
self.ivec |= RX_INT;
|
||||
} else {
|
||||
match &mut self.tx_callback {
|
||||
Some(cb) => (cb)(c),
|
||||
None => {}
|
||||
};
|
||||
self.tx_queue.push_front(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_keyboard(&mut self, val: u8) {
|
||||
let mut ctx = &mut self.ports[PORT_1];
|
||||
ctx.rx_data = val;
|
||||
ctx.stat |= STS_RXR;
|
||||
self.istat |= ISTS_RBI;
|
||||
self.ivec |= KEYBOARD_INT;
|
||||
}
|
||||
|
||||
pub fn vertical_blank(&mut self) {
|
||||
self.ivec |= MOUSE_BLANK_INT;
|
||||
self.ipcr |= 0x40;
|
||||
|
@ -221,6 +212,10 @@ impl Duart {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn tx_poll(&mut self) -> Option<u8> {
|
||||
self.tx_queue.pop_back()
|
||||
}
|
||||
|
||||
pub fn mouse_down(&mut self, button: u8) {
|
||||
self.ipcr = 0;
|
||||
self.inprt |= 0xb;
|
||||
|
@ -268,6 +263,31 @@ impl Duart {
|
|||
return (ctx.stat & STS_RXR) != 0;
|
||||
}
|
||||
|
||||
pub fn rx_keyboard(&mut self, c: u8) -> Result<(), DuartError> {
|
||||
let mut ctx = &mut self.ports[PORT_1];
|
||||
|
||||
if ctx.rx_pending {
|
||||
if Instant::now() > ctx.next_rx {
|
||||
if ctx.conf & CNF_ERX != 0 {
|
||||
ctx.rx_pending = false;
|
||||
ctx.rx_data = c;
|
||||
ctx.stat |= STS_RXR;
|
||||
self.istat |= ISTS_RBI;
|
||||
self.ivec |= KEYBOARD_INT;
|
||||
} else {
|
||||
ctx.stat |= STS_OER;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(DuartError::ReceiverNotReady)
|
||||
}
|
||||
} else {
|
||||
ctx.next_rx = Instant::now() + ctx.char_delay;
|
||||
ctx.rx_pending = true;
|
||||
Err(DuartError::ReceiverNotReady)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rx_char(&mut self, c: u8) -> Result<(), DuartError> {
|
||||
let mut ctx = &mut self.ports[PORT_0];
|
||||
|
||||
|
|
Loading…
Reference in New Issue