diff --git a/src/main.rs b/src/main.rs index fb18bd1..08f0583 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ mod option_window; mod ram; mod state; mod window; +mod twosio; use std::collections::HashMap; diff --git a/src/option_window.rs b/src/option_window.rs index 3933b16..6ad4f78 100644 --- a/src/option_window.rs +++ b/src/option_window.rs @@ -19,6 +19,7 @@ pub struct OptionWindow { category: OptionsCategory, card_options: Vec<(&'static Type, Box)>, select_idx: Option, + allow_changing_cards: bool, } #[derive(PartialEq, Eq)] @@ -41,13 +42,16 @@ impl OptionWindow { category: OptionsCategory::General, card_options, select_idx, + allow_changing_cards: !state.power(), } } fn sync_settings(&self, state: &mut EmuState) { state.update_options(self.options); - let cards = self.card_options.iter().map(|(typ, settings_ui)| (*typ, typ.new_card(ron::from_str(&settings_ui.serialize_settings()).unwrap()).unwrap())).collect(); - state.set_cards(cards); + if self.allow_changing_cards { + let cards = self.card_options.iter().map(|(typ, settings_ui)| (*typ, typ.new_card(ron::from_str(&settings_ui.serialize_settings()).unwrap()).unwrap())).collect(); + state.set_cards(cards); + } } } @@ -69,6 +73,7 @@ impl Window for OptionWindow { } OptionsCategory::Cards => { TopBottomPanel::top("card_opts").show_inside(ui, |ui| { + ui.set_enabled(self.allow_changing_cards); SidePanel::left("card_opts_left") .show_separator_line(false) .show_inside(ui, |ui| { diff --git a/src/state.rs b/src/state.rs index 35e86d6..857caf1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -15,6 +15,7 @@ pub struct EmuState { options: Options, fp_state: FrontpanelState, cards: Vec<(&'static Type, Box)>, + io_cache: Option<(u8, u8)> } impl EmuState { @@ -34,6 +35,7 @@ impl EmuState { options, fp_state: FrontpanelState::default(), cards, + io_cache: None, }; slf.apply_options(); slf @@ -125,8 +127,10 @@ impl EmuState { | MemCycle::Hlta => { self.fp_state.set_data(0xff); } - MemCycle::In(_) => { - self.fp_state.set_data(0); + MemCycle::In(a) => { + let data = self.read_io(a as u8); + self.io_cache = Some((a as u8, data)); + self.fp_state.set_data(data); } MemCycle::Inta(_) => todo!(), MemCycle::IntaHlt(_) => todo!(), @@ -141,8 +145,22 @@ impl EmuState { self.write_mem(a, d); 0 } - MemCycle::In(_) => 0, - MemCycle::Out(_, _) => 0, + MemCycle::In(a) => { + if let Some((cached_addr, cached_data)) = self.io_cache { + if cached_addr == a as u8 { + self.io_cache = None; + cached_data + } else { + self.read_io(a as u8) + } + } else { + self.read_io(a as u8) + } + }, + MemCycle::Out(a, d) => { + self.write_io(a as u8, d); + 0 + }, MemCycle::Inta(_) => todo!(), MemCycle::Hlta => { self.running = false; @@ -211,10 +229,22 @@ impl EmuState { self.cards.iter().map(|(typ, card)| (typ.name().to_string(), card.get_settings())).collect() } + pub fn power(&self) -> bool { + self.fp_state.power() + } + fn read_mem(&mut self, address: u16) -> u8 { self.cards.iter_mut().find_map(|(_, card)| card.read_mem(address)).unwrap_or(0xFF) } fn write_mem(&mut self, address: u16, data: u8) { self.cards.iter_mut().find_map(|(_, card)| card.write_mem(address, data)); } + + fn read_io(&mut self, address: u8) -> u8 { + self.cards.iter_mut().find_map(|(_, card)| card.read_io(address)).unwrap_or(0xFF) + } + + fn write_io(&mut self, address: u8, data: u8) { + self.cards.iter_mut().find_map(|(_, card)| card.write_io(address, data)); + } } diff --git a/src/twosio.rs b/src/twosio.rs new file mode 100644 index 0000000..154aa52 --- /dev/null +++ b/src/twosio.rs @@ -0,0 +1,108 @@ +use bitflags::bitflags; +use eframe::egui::{Ui, ComboBox}; +use serde::{Deserialize, Serialize}; + +use crate::{ + card::{Card, SettingsUi}, + register, +}; + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + struct Status: u8 { + const RDRF = 0b0000_0001; + const TDRE = 0b0000_0010; + const DCD = 0b0000_0100; + const CTS = 0b0000_1000; + const FE = 0b0001_0000; + const OVRN = 0b0010_0000; + const PE = 0b0100_0000; + const IRQ = 0b1000_0000; + } +} + +#[derive(Serialize, Deserialize)] +pub struct TwoSioSettings { + start_addr: u8, +} + +pub struct TwoSio { + start_addr: u8, + port1_status: Status, + port1_recv_data: u8, + port2_status: Status, + port2_recv_data: u8, +} + +impl Card for TwoSio { + fn new(settings: ron::Value) -> Result { + let settings: TwoSioSettings = settings.into_rust().unwrap(); + Ok(Self { + start_addr: settings.start_addr, + port1_status: Status::TDRE, + port1_recv_data: 0, + port2_status: Status::TDRE, + port2_recv_data: 0, + }) + } + + fn default_settings() -> String { + ron::to_string(&TwoSioSettings { + start_addr: 0, + }) + .unwrap() + } + + fn get_settings(&self) -> String { + ron::to_string(&TwoSioSettings { + start_addr: self.start_addr, + }).unwrap() + } + + fn settings_ui(settings: ron::Value) -> anyhow::Result + where + Self: Sized, + { + Ok(TwoSioSettingsUi { + settings: settings.into_rust().unwrap(), + }) + } + + fn read_io(&mut self, address: u8) -> Option { + if address & 0xF8 == self.start_addr { + match address & 0x2 { + 0 => Some(self.port1_status.bits()), + 1 => Some(self.port1_recv_data), + 2 => Some(self.port2_status.bits()), + 3 => Some(self.port2_recv_data), + _ => unreachable!(), + } + } else { + None + } + } +} + +pub struct TwoSioSettingsUi { + settings: TwoSioSettings, +} + +impl SettingsUi for TwoSioSettingsUi { + fn draw_ui(&mut self, ui: &mut Ui) { + ui.horizontal(|ui| { + ui.label("Base address"); + ComboBox::from_id_source("2sio_base").selected_text(format!("{:#x}", self.settings.start_addr)).show_ui(ui, |ui| { + for i in 0..64 { + let start_addr = (i as u8) * 4; + ui.selectable_value(&mut self.settings.start_addr, start_addr, format!("{:#x}", start_addr)); + } + }); + }); + } + + fn serialize_settings(&self) -> String { + ron::to_string(&self.settings).unwrap() + } +} + +register!(TwoSio, "88-2SIO");