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}