lc3_ensemble/sim/
observer.rs

1//! Module handles memory access observers,
2//! which store which accesses occur at a given memory location.
3//! 
4//! You would typically access an observer via the [`Simulator::observer`] field.
5//! This [`AccessObserver`] can be used to read or update accesses via its [`get_mem_accesses`]
6//! and [`update_mem_accesses`] methods.
7//! 
8//! [`Simulator::observer`]: crate::sim::Simulator::observer
9//! [`get_mem_accesses`]: AccessObserver::get_mem_accesses
10//! [`update_mem_accesses`]: AccessObserver::update_mem_accesses
11
12use std::collections::BTreeMap;
13
14/// The set of accesses which have occurred at this location.
15/// 
16/// ## Example
17/// 
18/// ```
19/// # use lc3_ensemble::sim::observer::AccessSet;
20/// 
21/// let accesses = AccessSet::READ;
22/// assert!(accesses.accessed());
23/// assert!(accesses.read());
24/// assert!(!accesses.written());
25/// assert!(!accesses.modified());
26/// ```
27#[derive(Default, Clone, Copy)]
28pub struct AccessSet(u8);
29impl AccessSet {
30    /// Set with only the read flag enabled.
31    pub const READ: Self = Self(1 << 0);
32    /// Set with only the write flag enabled.
33    pub const WRITTEN: Self = Self(1 << 1);
34    /// Set with only the modify flag enabled.
35    pub const MODIFIED: Self = Self(1 << 2);
36
37    /// True if any access has occurred.
38    pub fn accessed(&self) -> bool {
39        self.0 != 0
40    }
41    
42    /// True if a read has occurred.
43    pub fn read(&self) -> bool {
44        self.0 & Self::READ.0 != 0
45    }
46    /// True if a write has occurred (does not necessarily have to change data).
47    pub fn written(&self) -> bool {
48        self.0 & Self::WRITTEN.0 != 0
49    }
50    /// True if a write has occurred (data must change).
51    pub fn modified(&self) -> bool {
52        self.0 & Self::MODIFIED.0 != 0
53    }
54}
55impl std::ops::BitOr for AccessSet {
56    type Output = Self;
57
58    fn bitor(self, rhs: Self) -> Self::Output {
59        Self(self.0 | rhs.0)
60    }
61}
62impl std::ops::BitOrAssign for AccessSet {
63    fn bitor_assign(&mut self, rhs: Self) {
64        *self = *self | rhs;
65    }
66}
67impl std::fmt::Debug for AccessSet {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        f.debug_struct("AccessFlags")
70            .field("accessed", &self.accessed())
71            .field("read", &self.read())
72            .field("written", &self.written())
73            .field("modified", &self.modified())
74            .finish()
75    }
76}
77
78/// A struct that tracks accesses in memory.
79#[derive(Debug)]
80pub struct AccessObserver {
81    mem: BTreeMap<u16, AccessSet>
82    // Maybe add reg, PC, PSR, MCR support here?
83    //
84    // Memory can be easily tracked through the write_mem method,
85    // but reg/PC/PSR/MCR aren't exactly encapsulated in this way.
86}
87impl AccessObserver {
88    /// Creates a new access observer.
89    pub fn new() -> Self {
90        Self {
91            mem: Default::default()
92        }
93    }
94
95    /// Clears all accesses.
96    pub fn clear(&mut self) {
97        std::mem::take(self);
98    }
99
100    /// Gets the access set for the given memory location.
101    pub fn get_mem_accesses(&self, addr: u16) -> AccessSet {
102        self.mem.get(&addr).copied().unwrap_or_default()
103    }
104
105    /// Adds new flags to the access set for the given memory location.
106    pub fn update_mem_accesses(&mut self, addr: u16, set: AccessSet) {
107        *self.mem.entry(addr).or_default() |= set;
108    }
109    
110    /// Takes all memory accesses which have occurred since last clear,
111    /// as well as clearing memory accesses.
112    /// 
113    /// This iterator is sorted in address order.
114    pub fn take_mem_accesses(&mut self) -> impl Iterator<Item=(u16, AccessSet)> {
115        std::mem::take(&mut self.mem).into_iter()
116    }
117}
118impl Default for AccessObserver {
119    fn default() -> Self {
120        Self::new()
121    }
122}