268 lines
8.0 KiB
Rust
268 lines
8.0 KiB
Rust
#![allow(clippy::unreadable_literal)]
|
|
|
|
use crate::duart::Duart;
|
|
use crate::err::BusError;
|
|
use crate::mem::Mem;
|
|
use crate::mouse::Mouse;
|
|
|
|
use std::fmt::Debug;
|
|
use std::ops::Range;
|
|
|
|
const NVRAM_SIZE: usize = 8192;
|
|
|
|
/// 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_range(&self) -> &Range<usize>;
|
|
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>;
|
|
}
|
|
|
|
//
|
|
// Bus Memory Map
|
|
//
|
|
// 0x000000..0x01ffff ROM
|
|
// 0x200000..0x20003f DUART (Port A: host, Port B: keyboard/printer)
|
|
// 0x300000..0x3000ff 8530 SCC on optional I/O board
|
|
// 0x400000..0x400003 Mouse X/Y data
|
|
// 0x500000..0x500001 Display starting addr
|
|
// 0x600000..0x601fff BBRAM (Non-volatile RAM)
|
|
// 0x700000..0x7fffff RAM (256K or 1M)
|
|
//
|
|
|
|
pub struct Bus {
|
|
rom: Mem,
|
|
duart: Duart,
|
|
mouse: Mouse,
|
|
vid: Mem, // TODO: Figure out what device this really is
|
|
bbram: Mem, // TODO: change to BBRAM when implemented
|
|
ram: Mem,
|
|
video_ram_dirty: bool,
|
|
}
|
|
|
|
impl Bus {
|
|
pub fn new(mem_size: usize) -> Bus {
|
|
Bus {
|
|
rom: Mem::new(0, 0x20000, true),
|
|
duart: Duart::new(),
|
|
mouse: Mouse::new(),
|
|
vid: Mem::new(0x500000, 0x2, false),
|
|
bbram: Mem::new(0x600000, 0x2000, false),
|
|
ram: Mem::new(0x700000, mem_size, false),
|
|
video_ram_dirty: false,
|
|
}
|
|
}
|
|
|
|
fn get_device(&mut self, address: usize) -> Result<&mut dyn Device, BusError> {
|
|
if address < 0x20000 {
|
|
return Ok(&mut self.rom);
|
|
}
|
|
|
|
if (0x200000..0x200040).contains(&address) {
|
|
return Ok(&mut self.duart);
|
|
}
|
|
|
|
if (0x400000..0x400004).contains(&address) {
|
|
return Ok(&mut self.mouse);
|
|
}
|
|
|
|
if (0x500000..0x500002).contains(&address) {
|
|
return Ok(&mut self.vid);
|
|
}
|
|
|
|
if (0x600000..0x602000).contains(&address) {
|
|
return Ok(&mut self.bbram);
|
|
}
|
|
|
|
if (0x700000..0x800000).contains(&address) {
|
|
return Ok(&mut self.ram);
|
|
}
|
|
|
|
Err(BusError::NoDevice(address))
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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> {
|
|
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(address));
|
|
}
|
|
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(address));
|
|
}
|
|
self.get_device(address)?.read_word(address, access)
|
|
}
|
|
|
|
pub fn read_op_half(&mut self, address: usize) -> Result<u16, BusError> {
|
|
let m = self.get_device(address)?;
|
|
|
|
Ok(u16::from(m.read_byte(address, AccessCode::OperandFetch)?)
|
|
| u16::from(m.read_byte(address + 1, AccessCode::OperandFetch)?).wrapping_shl(8))
|
|
}
|
|
|
|
pub fn read_op_word(&mut self, address: usize) -> Result<u32, BusError> {
|
|
let m = self.get_device(address)?;
|
|
|
|
Ok(u32::from(m.read_byte(address, AccessCode::OperandFetch)?)
|
|
| u32::from(m.read_byte(address + 1, AccessCode::OperandFetch)?).wrapping_shl(8)
|
|
| u32::from(m.read_byte(address + 2, AccessCode::OperandFetch)?).wrapping_shl(16)
|
|
| u32::from(m.read_byte(address + 3, AccessCode::OperandFetch)?).wrapping_shl(24))
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
pub fn write_half(&mut self, address: usize, val: u16) -> Result<(), BusError> {
|
|
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)
|
|
}
|
|
|
|
pub fn write_word(&mut self, address: usize, val: u32) -> Result<(), BusError> {
|
|
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)
|
|
}
|
|
|
|
pub fn load(&mut self, address: usize, data: &[u8]) -> Result<(), BusError> {
|
|
self.get_device(address)?.load(address, data)
|
|
}
|
|
|
|
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) {
|
|
self.duart.service();
|
|
}
|
|
|
|
pub fn get_interrupts(&mut self) -> Option<u8> {
|
|
self.duart.get_interrupt()
|
|
}
|
|
|
|
pub fn mouse_move(&mut self, x: u16, y: u16) {
|
|
self.mouse.x = x;
|
|
self.mouse.y = y;
|
|
}
|
|
|
|
pub fn mouse_down(&mut self, button: u8) {
|
|
self.duart.mouse_down(button);
|
|
}
|
|
|
|
pub fn mouse_up(&mut self, button: u8) {
|
|
self.duart.mouse_up(button);
|
|
}
|
|
|
|
pub fn rs232_tx(&mut self) -> Option<u8> {
|
|
self.duart.rs232_tx()
|
|
}
|
|
|
|
pub fn keyboard_tx(&mut self) -> Option<u8> {
|
|
self.duart.keyboard_tx()
|
|
}
|
|
|
|
pub fn rs232_rx(&mut self, c: u8) {
|
|
self.duart.rs232_rx(c);
|
|
}
|
|
|
|
pub fn keyboard_rx(&mut self, keycode: u8) {
|
|
self.duart.keyboard_rx(keycode);
|
|
}
|
|
|
|
pub fn duart_output(&self) -> u8 {
|
|
self.duart.output_port()
|
|
}
|
|
|
|
pub fn get_nvram(&self) -> &[u8] {
|
|
self.bbram.as_slice(0..NVRAM_SIZE)
|
|
}
|
|
|
|
pub fn set_nvram(&mut self, nvram: &[u8]) {
|
|
for (i, b) in nvram.iter().enumerate() {
|
|
self.bbram[i] = *b;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn should_fail_on_alignment_errors() {
|
|
let mut bus: Bus = Bus::new(0x10000);
|
|
|
|
assert!(bus.write_byte(0x700000, 0x1f).is_ok());
|
|
assert!(bus.write_half(0x700000, 0x1f1f).is_ok());
|
|
assert!(bus.write_word(0x700000, 0x1f1f1f1f).is_ok());
|
|
assert!(bus.write_half(0x700001, 0x1f1f).is_err());
|
|
assert!(bus.write_half(0x700002, 0x1f1f).is_ok());
|
|
assert!(bus.write_word(0x700001, 0x1f1f1f1f).is_err());
|
|
assert!(bus.write_word(0x700002, 0x1f1f1f1f).is_err());
|
|
assert!(bus.write_word(0x700003, 0x1f1f1f1f).is_err());
|
|
assert!(bus.write_word(0x700004, 0x1f1f1f1f).is_ok());
|
|
}
|
|
}
|