diff --git a/src/component/memory.rs b/src/component/memory.rs new file mode 100644 index 0000000..283a992 --- /dev/null +++ b/src/component/memory.rs @@ -0,0 +1,112 @@ +use rassert_rs::rassert; + +use super::Component; +use crate::sim::{Event, UserEvent, UserEventError}; +use crate::circuit::Params; +use crate::util::*; + +#[derive(Debug, Clone)] +pub struct Memory { + // 0 - read + // 1 - write + rw_select: bool, + // "active-low" (true for disconnect, false for connected) + chip_select: bool, + address: Bits, + data_in: Bits, + data_out: Bits, + + storage: Vec, + changed: bool, + delay: u32, +} + +impl Component for Memory { + fn evaluate(&self) -> Option> { + if !self.changed { return None } + + // We only need to return Some since the address bits specify + // which storage word to access for read or write, not the actual + // event, i.e. the event is just used a signal + Some(vec![(0, false)]) + } + + fn update(&mut self, _event: Event) { + let word_address = self.address.to_number(); + match self.rw_select { + // Read + false => { + self.storage[word_address as usize] = self.data_in.clone(); + }, + // Write + true => { + self.data_out = self.storage[word_address as usize].clone(); + }, + } + } + + fn set_pin(&mut self, pin: u32, event: Event) { + // Skip if the pin being set isn't CS or if the chip select isn't set + if pin != 0 || self.chip_select { return } + + self.changed = true; + + let alen = self.address.len(); + let dlen = self.data_in.len(); + let end = alen + dlen; + + match pin as usize { + 0 => self.rw_select = event.value, + 1 => self.chip_select = event.value, + n if (2..alen).contains(&n) => self.address.set_bit(n - 2, event.value), + n if (alen..end).contains(&n) => self.data_in.set_bit(n - alen - 2, event.value), + _ => {} + } + } + + fn get_state(&self) -> serde_json::Value { + let words = self.storage.iter() + .map(|word| word.to_vec()) + .collect::>>(); + + serde_json::json!({"storage": words}) + } + + fn delay(&self) -> u32 { + self.delay + } + + fn is_source(&self) -> bool { + false + } + + fn is_output(&self) -> bool { + false + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl Default for Memory { + fn default() -> Self { + Self { + rw_select: false, + chip_select: true, + address: Bits::new(16), + data_in: Bits::new(8), + data_out: Bits::new(8), + storage: vec![Bits::new(8); 16 * 1024], + changed: false, + delay: 1, + } + } +} + +impl Memory { + pub fn from_params(_params: Params) -> Self { + Default::default() + } +} + diff --git a/src/component/mod.rs b/src/component/mod.rs index 1fdcd94..07e2a14 100644 --- a/src/component/mod.rs +++ b/src/component/mod.rs @@ -8,6 +8,7 @@ mod ground; mod source; mod clock; mod led; +mod memory; pub use definition::ComponentDefinition; pub use tristate::Tristate; @@ -19,6 +20,7 @@ pub use ground::Ground; pub use source::Source; pub use clock::Clock; pub use led::Led; +pub use memory::Memory; use std::any::Any; use std::fmt::Debug; diff --git a/src/lib.rs b/src/lib.rs index 6927543..e3fc8b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,9 @@ pub mod circuit; pub mod component; pub mod sim; pub mod wasm; +pub mod util; pub use circuit::Circuit; pub use component::Component; pub use sim::Simulation; +pub use util::*; diff --git a/src/util/bitext.rs b/src/util/bitext.rs new file mode 100644 index 0000000..cd55dd1 --- /dev/null +++ b/src/util/bitext.rs @@ -0,0 +1,38 @@ + + +pub trait BitExt { + fn check_bit(&self, n: usize) -> bool; + fn set_bit_to(&mut self, n: usize, value: bool); + fn set_bit(&mut self, n: usize); + fn clear_bit(&mut self, n: usize); +} + + +macro_rules! bitext_impl_for { + ($t: ty) => { + impl BitExt for $t { + fn check_bit(&self, n: usize) -> bool { + *self & (1 << n) != 0 + } + + fn set_bit_to(&mut self, n: usize, value: bool) { + const LUT: &[$t] = &[<$t>::MIN, <$t>::MAX]; + let value = LUT[value as usize]; + *self ^= (!value ^ *self) & (1 << n); + } + + fn set_bit(&mut self, n: usize) { + *self |= 1 << n; + } + + fn clear_bit(&mut self, n: usize) { + *self &= !(1 << n); + } + } + }; +} + +bitext_impl_for!(u64); +bitext_impl_for!(u32); +bitext_impl_for!(u16); +bitext_impl_for!(u8); diff --git a/src/util/bits.rs b/src/util/bits.rs new file mode 100644 index 0000000..4c4511c --- /dev/null +++ b/src/util/bits.rs @@ -0,0 +1,72 @@ +pub use super::bitext::BitExt; +use BitsEnum::*; + +#[derive(Debug, Clone, PartialEq, Eq)] +enum BitsEnum { + Optimized(u64), + Dynamic(Vec), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Bits { + inner: BitsEnum, + size: usize, +} + +impl Bits { + pub fn new(n: usize) -> Self { + let inner = if n < 64 { + Optimized(0) + } else { + let mut bits = Vec::new(); + bits.resize(n, false); + Dynamic(bits) + }; + + Self { + inner, + size: n, + } + } + + pub fn set_bit(&mut self, n: usize, value: bool) { + debug_assert!(n < self.size, "Cannot access out-of-bounds bit."); + match &mut self.inner { + Optimized(bits) => bits.set_bit_to(n, value), + Dynamic(bits) => bits[n] = value, + } + } + + pub fn get_bit(&self, n: usize) -> bool { + debug_assert!(n < self.size, "Cannot access out-of-bounds bit."); + + match &self.inner { + Optimized(bits) => bits.check_bit(n), + Dynamic(bits) => bits[n], + } + } + + pub fn to_number(&self) -> u64 { + match &self.inner { + Optimized(bits) => *bits & self.size as u64, + Dynamic(bits) => bits.iter().rev().enumerate().map(|(i, &x)| i as u64 * x as u64).sum(), + } + } + + pub fn len(&self) -> usize { + self.size + } + + pub fn to_vec(&self) -> Vec { + match &self.inner { + Optimized(bits) => { + let mut res = Vec::with_capacity(self.size); + for i in 0..self.size { + res.push(bits.check_bit(i)); + } + res + }, + Dynamic(bits) => bits.clone(), + } + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..33f334a --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,3 @@ +mod bits; +pub(crate) mod bitext; +pub use bits::*;