lc3_ensemble/sim/
mem.rs

1//! Memory handling for the LC-3 simulator.
2//! 
3//! This module consists of:
4//! - [`Word`]: A mutable memory location.
5//! - [`MemArray`]: The memory array.
6//! - [`RegFile`]: The register file.
7
8use rand::rngs::StdRng;
9use rand::Rng;
10
11use crate::ast::Reg;
12
13/// A memory location that can be read and written to.
14/// 
15/// # Reading
16/// 
17/// A word's value can be read with:
18/// - [`Word::get`] to directly access the value, ignoring any initialization state
19/// - [`Word::get_if_init`] to directly access the value after verifying initialization state
20/// 
21/// See the respective functions for more details.
22/// 
23/// Both functions return the unsigned representation of the word.
24/// If needed, this can be converted to a signed integer with typical `as` casting (`data as i16`).
25/// 
26/// # Writing
27/// 
28/// A word can be written into with a value or with another word:
29/// - [`Word::set`] to read a value into this word
30/// - [`Word::set_if_init`] to read a word into this word
31/// 
32/// [`Word::set_if_init`] may be more useful in situations where initialization state needs to be preserved
33/// or when it needs to be verified.
34/// 
35/// See the respective functions for more details.
36/// 
37/// Words can also be written to by applying assign operations (e.g., add, sub, and, etc.).
38/// All arithmetic operations that can be applied to words are assumed to be wrapping.
39/// See those implementations for more details.
40/// 
41/// # Initialization
42/// 
43/// Internally, each memory location keeps track of two fields:
44/// 1. its data (i.e., the value stored at this location)
45/// 2. which bits of its data are truly "initialized" (as in the program knows what values are present there)
46/// 
47/// This second field is not used except for when the simulator is set to strict mode.
48/// Then, this second field is leveraged to detect if uninitialized memory is being
49/// written to places it shouldn't be (e.g., PC, addresses, registers and memory).
50/// 
51/// When a `Word` is created for memory/register files (i.e., via [`Word::new_uninit`]), 
52/// it is created with the initialization bits set to fully uninitialized.
53/// The data associated with this `Word` is decided by the creation strategy 
54/// (see [`super::MachineInitStrategy`] for details).
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub struct Word {
57    data: u16,
58    init: u16
59}
60
61const NO_BITS:  u16 = 0;
62const ALL_BITS: u16 = 1u16.wrapping_neg();
63
64impl Word {
65    /// Creates a new word that is considered uninitialized.
66    pub fn new_uninit<F: WordFiller + ?Sized>(fill: &mut F) -> Self {
67        Self {
68            data: fill.generate(),
69            init: NO_BITS,
70        }
71    }
72    /// Creates a new word that is initialized with a given data value.
73    pub fn new_init(data: u16) -> Self {
74        Self {
75            data,
76            init: ALL_BITS,
77        }
78    }
79
80    /// Reads the word, returning its unsigned representation.
81    /// 
82    /// The data is returned without checking for initialization state.
83    /// If the initialization state should be checked before trying to query the data,
84    /// then [`Word::get_if_init`] should be used instead.
85    pub fn get(&self) -> u16 {
86        self.data
87    }
88    /// Reads the word if it is properly initialized under strictness requirements, returning its unsigned representation.
89    /// 
90    /// This function is more cognizant of word initialization than [`Word::get`].
91    /// - In non-strict mode (`strict == false`), this function unconditionally allows access to the data regardless of initialization state.
92    /// - In strict mode (`strict == true`), this function verifies `self` is fully initialized, raising the provided error if not.
93    pub fn get_if_init<E>(&self, strict: bool, err: E) -> Result<u16, E> {
94        match !strict || self.is_init() {
95            true  => Ok(self.data),
96            false => Err(err)
97        }
98    }
99
100    /// Writes to the word.
101    /// 
102    /// This sets the word to the `data` value assuming it is **fully** initialized
103    /// and correspondingly sets the initialization state to be fully initialized.
104    /// 
105    /// If the initialization state of the `data` value should be checked before
106    /// trying to write to the word, then [`Word::set_if_init`] should be used instead.
107    pub fn set(&mut self, data: u16) {
108        self.data = data;
109        self.init = ALL_BITS;
110    }
111    /// Writes to the word while verifying the data stored is properly initialized under strictness requirements.
112    /// 
113    /// This function is more cognizant of word initialization than [`Word::set`].
114    /// - In non-strict mode, this function preserves the initialization data of the `data` argument.
115    /// - In strict mode, this function verifies `data` is fully initialized, raising the provided error if not.
116    pub fn set_if_init<E>(&mut self, data: Word, strict: bool, err: E) -> Result<(), E> {
117        match !strict || data.is_init() {
118            true => {
119                *self = data;
120                Ok(())
121            },
122            false => Err(err)
123        }
124    }
125
126    /// Checks that a word is fully initialized
127    pub fn is_init(&self) -> bool {
128        self.init == ALL_BITS
129    }
130    /// Clears initialization of this word.
131    pub fn clear_init(&mut self) {
132        self.init = NO_BITS;
133    }
134}
135impl From<u16> for Word {
136    /// Creates a fully initialized word.
137    fn from(value: u16) -> Self {
138        Word::new_init(value)
139    }
140}
141impl From<i16> for Word {
142    /// Creates a fully initialized word.
143    fn from(value: i16) -> Self {
144        Word::new_init(value as u16)
145    }
146}
147
148impl std::ops::Not for Word {
149    type Output = Word;
150
151    /// Inverts the data on this word, preserving any initialization state.
152    fn not(self) -> Self::Output {
153        // Initialization state should stay the same after this.
154        let Self { data, init } = self;
155        Self { data: !data, init }
156    }
157}
158
159
160impl std::ops::Add for Word {
161    type Output = Word;
162
163    /// Adds two words together (wrapping if overflow occurs).
164    /// 
165    /// If the two words are fully initialized, 
166    /// the resulting word will also be fully initialized.
167    /// Otherwise, the resulting word is fully uninitialized.
168    fn add(self, rhs: Self) -> Self::Output {
169        let Self { data: ldata, init: linit } = self;
170        let Self { data: rdata, init: rinit } = rhs;
171
172        if rdata == 0 && rinit == ALL_BITS { return self; }
173        if ldata == 0 && linit == ALL_BITS { return rhs; }
174
175        let data = ldata.wrapping_add(rdata);
176
177        // Close enough calculation:
178        // If both are fully init, consider this word fully init.
179        // Otherwise, consider it fully uninit.
180        let init = match linit == ALL_BITS && rinit == ALL_BITS {
181            true  => ALL_BITS,
182            false => NO_BITS,
183        };
184
185        Self { data, init }
186    }
187}
188impl std::ops::AddAssign for Word {
189    fn add_assign(&mut self, rhs: Self) {
190        *self = *self + rhs;
191    }
192}
193impl std::ops::AddAssign<u16> for Word {
194    /// Increments the word by the provided value.
195    /// 
196    /// If the word was fully initialized,
197    /// its updated value is also fully initialized.
198    /// Otherwise, the resulting word is fully uninitialized.
199    fn add_assign(&mut self, rhs: u16) {
200        *self = *self + Word::from(rhs);
201    }
202}
203impl std::ops::AddAssign<i16> for Word {
204    /// Increments the word by the provided value.
205    /// 
206    /// If the word was fully initialized,
207    /// its updated value is also fully initialized.
208    /// Otherwise, the resulting word is fully uninitialized.
209    fn add_assign(&mut self, rhs: i16) {
210        *self = *self + Word::from(rhs);
211    }
212}
213
214
215impl std::ops::Sub for Word {
216    type Output = Word;
217
218    /// Subtracts two words together (wrapping if overflow occurs).
219    /// 
220    /// If the two words are fully initialized, 
221    /// the resulting word will also be fully initialized.
222    /// Otherwise, the resulting word is fully uninitialized.
223    fn sub(self, rhs: Self) -> Self::Output {
224        let Self { data: ldata, init: linit } = self;
225        let Self { data: rdata, init: rinit } = rhs;
226
227        // This is (self - 0) == self.
228        if rdata == 0 && rinit == ALL_BITS { return self; }
229
230        let data = ldata.wrapping_sub(rdata);
231        // Very lazy initialization scheme.
232        // If both are fully init, consider this word fully init.
233        // Otherwise, consider it fully uninit.
234        let init = match linit == ALL_BITS && rinit == ALL_BITS {
235            true  => ALL_BITS,
236            false => NO_BITS,
237        };
238
239        Self { data, init }
240    }
241}
242impl std::ops::SubAssign for Word {
243    fn sub_assign(&mut self, rhs: Self) {
244        *self = *self - rhs;
245    }
246}
247impl std::ops::SubAssign<u16> for Word {
248    /// Decrements the word by the provided value.
249    /// 
250    /// If the word was fully initialized,
251    /// its updated value is also fully initialized.
252    /// Otherwise, the resulting word is fully uninitialized.
253    fn sub_assign(&mut self, rhs: u16) {
254        *self = *self - Word::new_init(rhs);
255    }
256}
257impl std::ops::SubAssign<i16> for Word {
258    /// Decrements the word by the provided value.
259    /// 
260    /// If the word was fully initialized,
261    /// its updated value is also fully initialized.
262    /// Otherwise, the resulting word is fully uninitialized.
263    fn sub_assign(&mut self, rhs: i16) {
264        *self = *self - Word::new_init(rhs as _);
265    }
266}
267
268
269impl std::ops::BitAnd for Word {
270    type Output = Word;
271
272    /// Applies a bitwise AND across two words.
273    /// 
274    /// This will also compute the correct initialization
275    /// for the resulting word, taking into account bit clearing.
276    fn bitand(self, rhs: Self) -> Self::Output {
277        let Self { data: ldata, init: linit } = self;
278        let Self { data: rdata, init: rinit } = rhs;
279
280        let data = ldata & rdata;
281        // A given bit of the result is init if:
282        // - both the lhs and rhs bits are init
283        // - either of the bits are data: 0, init: 1
284        let init = (linit & rinit) | (!ldata & linit) | (!rdata & rinit);
285
286        Self { data, init }
287    }
288}
289impl std::ops::BitAndAssign for Word {
290    fn bitand_assign(&mut self, rhs: Self) {
291        *self = *self & rhs;
292    }
293}
294
295/// Trait that describes types that can be used to create the data for an uninitialized [`Word`].
296/// 
297/// This is used with [`Word::new_uninit`] to create uninitialized Words.
298pub trait WordFiller {
299    /// Generate a word of data.
300    fn generate(&mut self) -> u16;
301
302    /// Generates an array of [`Word`]s.
303    fn generate_array<const N: usize>(&mut self) -> [Word; N] {
304        std::array::from_fn(|_| Word::new_uninit(self))
305    }
306    /// Generates a heap-allocated array of [`Word`]s.
307    fn generate_boxed_array<const N: usize>(&mut self) -> Box<[Word; N]> {
308        std::iter::repeat_with(|| Word::new_uninit(self))
309            .take(N)
310            .collect::<Box<_>>()
311            .try_into()
312            .unwrap_or_else(|_| unreachable!("iterator should have had {N} elements"))
313    }
314}
315impl WordFiller for () {
316    /// This creates unseeded, non-deterministic values.
317    fn generate(&mut self) -> u16 {
318        rand::random()
319    }
320}
321impl WordFiller for u16 {
322    /// Sets each word to the given value.
323    fn generate(&mut self) -> u16 {
324        *self
325    }
326}
327impl WordFiller for StdRng {
328    /// This creates values from the standard random number generator.
329    /// 
330    /// This can be used to create deterministic, seeded values.
331    fn generate(&mut self) -> u16 {
332        self.gen()
333    }
334}
335/// Strategy used to initialize the `reg_file` and `mem` of the [`Simulator`].
336/// 
337/// These are used to set the initial state of the memory and registers,
338/// which will be treated as uninitialized until they are properly initialized
339/// by program code.
340/// 
341/// [`Simulator`]: super::Simulator
342#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
343pub enum MachineInitStrategy {
344    /// Initializes each word randomly and non-deterministically.
345    #[default]
346    Unseeded,
347
348    /// Initializes each word randomly and deterministically.
349    Seeded {
350        /// The seed the RNG was initialized with.
351        seed: u64
352    },
353
354    /// Initializes each word to a known value.
355    Known {
356        /// The value to initialize each value to.
357        value: u16
358    }
359}
360
361impl MachineInitStrategy {
362    pub(super) fn generator(&self) -> impl WordFiller {
363        use rand::SeedableRng;
364
365        match self {
366            MachineInitStrategy::Unseeded => WCGenerator::Unseeded,
367            MachineInitStrategy::Seeded { seed } => WCGenerator::Seeded(Box::new(StdRng::seed_from_u64(*seed))),
368            MachineInitStrategy::Known { value } => WCGenerator::Known(*value),
369        }
370    }
371}
372
373enum WCGenerator {
374    Unseeded,
375    Seeded(Box<rand::rngs::StdRng>),
376    Known(u16)
377}
378impl WordFiller for WCGenerator {
379    fn generate(&mut self) -> u16 {
380        match self {
381            WCGenerator::Unseeded  => ().generate(),
382            WCGenerator::Seeded(r) => r.generate(),
383            WCGenerator::Known(k)  => k.generate(),
384        }
385    }
386}
387
388/// Memory array.
389/// 
390/// This can be addressed with any `u16` (16-bit address).
391/// 
392/// This memory array *does* expose memory locations 0xFE00-0xFFFF,
393/// however they are not accessible through normal Simulator operation 
394/// (i.e., via [`Simulator::read_mem`]) and [`Simulator::write_mem`].
395/// 
396/// They can be read and edited via the typical Index traits.
397/// If you wish to see the handling of memory-mapped IO, see the above
398/// [`Simulator`] methods.
399/// 
400/// [`Simulator`]: super::Simulator
401/// [`Simulator::read_mem`]: super::Simulator::read_mem
402/// [`Simulator::write_mem`]: super::Simulator::write_mem
403/// [`Simulator::default_mem_ctx`]: super::Simulator::default_mem_ctx
404#[derive(Debug)]
405pub struct MemArray(Box<[Word; 1 << 16]>);
406impl MemArray {
407    /// Creates a new memory with a provided word creation strategy.
408    pub fn new(filler: &mut impl WordFiller) -> Self {
409        Self(filler.generate_boxed_array())
410    }
411
412    /// Copies an object file block into this memory.
413    pub(super) fn copy_obj_block(&mut self, mut start: u16, data: &[Option<u16>]) {
414        let mem = &mut self.0;
415
416        // chunk_by was added in Rust 1.77
417        struct ChunkBy<'s, T, F>(&'s [T], F);
418        impl<'s, T, F: FnMut(&T, &T) -> bool> Iterator for ChunkBy<'s, T, F> {
419            type Item = &'s [T];
420        
421            fn next(&mut self) -> Option<Self::Item> {
422                let (first, rest) = self.0.split_first()?;
423
424                // find the first element that doesn't match pred (+1 for the first el that was removed)
425                let pos = match rest.iter().position(|n| !(self.1)(first, n)) {
426                    Some(i) => i + 1,
427                    None => self.0.len(),
428                };
429
430                let (chunk, rest) = self.0.split_at(pos);
431
432                self.0 = rest;
433                Some(chunk)
434            }
435        }
436
437        // separate data into chunks of initialized/uninitialized
438        for chunk in ChunkBy(data, |a: &Option<_>, b: &Option<_>| a.is_some() == b.is_some()) {
439            let end = start.wrapping_add(chunk.len() as u16);
440
441            let si = usize::from(start);
442            let ei = usize::from(end);
443            let block_is_contiguous = start <= end;
444
445            if chunk[0].is_some() { // if chunk is init, copy the data over
446                let ch: Vec<_> = chunk.iter()
447                    .map(|&opt| opt.unwrap())
448                    .map(Word::new_init)
449                    .collect();
450
451                if block_is_contiguous {
452                    mem[si..ei].copy_from_slice(&ch);
453                } else {
454                    let (left, right) = ch.split_at(start.wrapping_neg() as usize);
455                    mem[si..].copy_from_slice(left);
456                    mem[..ei].copy_from_slice(right)
457                }
458            } else { // if chunk is uninit, clear the initialization state
459                if block_is_contiguous {
460                    for word in &mut mem[si..ei] {
461                        word.clear_init();
462                    }
463                } else {
464                    for word in &mut mem[si..] {
465                        word.clear_init();
466                    }
467                    for word in &mut mem[..ei] {
468                        word.clear_init();
469                    }
470                }
471            }
472
473            start = end;
474        }
475    }
476
477    pub(super) fn as_slice_mut(&mut self) -> &mut [Word] {
478        &mut *self.0
479    }
480}
481impl std::ops::Index<u16> for MemArray {
482    type Output = Word;
483
484    fn index(&self, index: u16) -> &Self::Output {
485        &self.0[index as usize]
486    }
487}
488impl std::ops::IndexMut<u16> for MemArray {
489    fn index_mut(&mut self, index: u16) -> &mut Self::Output {
490        &mut self.0[index as usize]
491    }
492}
493
494/// The register file. 
495/// 
496/// This struct can be indexed with a [`Reg`].
497/// 
498/// # Example
499/// 
500/// ```
501/// use lc3_ensemble::sim::mem::RegFile;
502/// use lc3_ensemble::ast::Reg::R0;
503/// 
504/// let mut reg = RegFile::new(&mut ()); // never should have to initialize a reg file
505/// reg[R0].set(11);
506/// assert_eq!(reg[R0].get(), 11);
507/// ```
508#[derive(Debug, Clone)]
509pub struct RegFile([Word; 8]);
510impl RegFile {
511    /// Creates a register file with uninitialized data.
512    pub fn new(filler: &mut impl WordFiller) -> Self {
513        Self(filler.generate_array())
514    }
515}
516impl std::ops::Index<Reg> for RegFile {
517    type Output = Word;
518
519    fn index(&self, index: Reg) -> &Self::Output {
520        &self.0[usize::from(index)]
521    }
522}
523impl std::ops::IndexMut<Reg> for RegFile {
524    fn index_mut(&mut self, index: Reg) -> &mut Self::Output {
525        &mut self.0[usize::from(index)]
526    }
527}