cpu/
memory.rs

1//! This module emulates the TX-2's STUV memory.
2//!
3//! The TX2 has several different kinds of memory.  Some of it (known
4//! as S, T, U and V memories) are directly addressible.  These are
5//! described in Chapter 11 (Volume 2) of the Technical Manual.
6//!
7//! The locations of the S, U and T memories are taken from page 5-13
8//! of the Users Handbook (which describes memory alarms).  The
9//! location of V memory was deduced from the descrption of the
10//! plugboard in the user guide (which states that the plugboards end
11//! at 0377777).
12//!
13//! STUV memory is memory-mapped.  That is, each location (which the
14//! documentation describes as a "register") has an address between 0
15//! and 377777 octal, inclusive.  Even registers that we would
16//! describe today as being "CPU registers" (i.e. registers A-E) have
17//! addresses.
18//!
19//! Other memories (for example X memory for index registers and F
20//! memory for configuration values) are emulated in the
21//! [`control`](crate::control) module and do not have addresses.
22//!
23//! # Storage of Words
24//!
25//! The TX-2 uses 36-bit words.  We use [`Unsigned36Bit`] to represent
26//! this.  The TX-2 has a number of memories which have differing
27//! widths.  Its STUV memory (which today we might describe as "main
28//! memory") has 38 bitplanes.  36 for each of the value bits, plus
29//! two more, the meta bit and the parity bit.
30//!
31//! # Metabits
32//!
33//! The meta bit of a location can be read or written using special
34//! memory-related instructions.  Programs can also set up a mode of
35//! operation in which various operations (e.g.  loading an operand
36//! or instruction) causes a meta bit to be set.
37//!
38//! # Parity Bits
39//!
40//! Parity bits are maintained and checked by the system.  They are
41//! readable via the SKM instruction.  The TX-2 had two distinct
42//! meanings for parity bits: one which is stored with the data, and
43//! the other with is computed as-needed from the data itself.  When
44//! the stored and computed parity bits differ, this is a parity error
45//! (which might, for example, cause an alarm to be raised).
46//!
47//! The emulator behaves as if parity errors never occur.  Therefore
48//! in computes both the "stored" and "computed" parity bits
49//! on-the-fly as needed.
50//!
51//! # Memory Map
52//!
53//! | Address | Description                                                  |
54//! |---------| ------------------------------------------------------------- |
55//! | 0000000 | Start of S memory (Technical Manual Volume 2, sec 12-2.8)     |
56//! | 0177777 | Last word of S memory (WJCC paper gives size as 65536 words)  |
57//! | 0200000 | Start of T memory                                             |
58//! | 0207777 | Last location in T memory.                                    |
59//! | 0210000 | Start of U memory                                             |
60//! | 0217777 | Last location in U memory.                                    |
61//! | 0377600 | Start of V-memory.                                            |
62//! | 0377604 | A register                                                    |
63//! | 0377605 | B register                                                    |
64//! | 0377606 | C register                                                    |
65//! | 0377607 | D register                                                    |
66//! | 0377610 | E register                                                    |
67//! | 0377620 | Knob (Shaft Encoder) Register (User Handbook, 5-20)           |
68//! | 0377621 | External Input Register (User Handbook, 5-20)                 |
69//! | 0377630 | Real Time Clock                                               |
70//! | 0377710 | Location of CODABO start point 0                              |
71//! | 0377711 | Location of CODABO start point 1                              |
72//! | 0377712 | Location of CODABO start point 2                              |
73//! | 0377713 | Location of CODABO start point 3                              |
74//! | 0377714 | Location of CODABO start point 4                              |
75//! | 0377715 | Location of CODABO start point 5                              |
76//! | 0377716 | Location of CODABO start point 6                              |
77//! | 0377717 | Location of CODABO start point 7                              |
78//! | 0377740 | **Plugboard B memory start**. The plugboard program code is given in section 5-5.2 (page 5-27) of the User Handbook. |
79//! | 0377740 | 8 (Octal 10) words of data used by `SPG` instructions of the code at 0377750 to set the standard configuration in F-memory. |
80//! | 0377750 | Standard program, **Set Configuration**. Loads the standard configuration into F-memory.  Then proceed to 0377760. |
81//! | 0377757 | Last location in Plugboard B. |
82//! | 0377760 | Plugboard A memory start |
83//! | 0377760 | Standard program, **Read In Reader Leader**. Reads the first 21 words from paper tape into registers 3 through 24 of S-memory, then goes to register 3.  The 21 words would be the "standard reader leader" of binary paper tapes.  The code for the standard reader leader is given in the User Handbook, section 5-5.2. |
84//! | 0377770 | Standard program, **Clear Memory / Smear Memory**. Sets all of S, T, U memory to 0 on the left and the address of itself on the right. Meta bits are not affected (User Guide 5-25). Automatically proceeds to 037750 (Set Configuration). |
85//! | 0377777 | Plugboard A memory end; end of V memory; end of memory. |
86//!
87use core::time::Duration;
88use std::error;
89use std::fmt::{self, Debug, Display, Formatter};
90#[cfg(test)]
91use std::ops::RangeInclusive;
92
93use tracing::{Level, event};
94
95use base::bitselect::BitSelector;
96use base::prelude::*;
97
98use super::context::Context;
99use mref::{MemoryRead, MemoryReadRef, MemoryWriteRef};
100
101pub(crate) const S_MEMORY_START: u32 = 0o0000000;
102pub(crate) const S_MEMORY_SIZE: u32 = 1 + 0o0177777;
103pub(crate) const T_MEMORY_START: u32 = 0o0200000;
104pub(crate) const T_MEMORY_SIZE: u32 = 1 + 0o0207777 - 0o0200000;
105pub(crate) const U_MEMORY_START: u32 = 0o0210000;
106pub(crate) const U_MEMORY_SIZE: u32 = 1 + 0o0217777 - 0o0210000;
107pub(crate) const V_MEMORY_START: u32 = 0o0377600;
108pub(crate) const V_MEMORY_SIZE: u32 = 1 + 0o0377777 - 0o0377600;
109
110//pub const STANDARD_PROGRAM_CLEAR_MEMORY: Address = Address::new(u18!(0o0377770));
111pub(crate) const STANDARD_PROGRAM_INIT_CONFIG: Address = Address::new(u18!(0o0377750));
112
113#[derive(Debug, Clone)]
114pub(crate) enum MemoryOpFailure {
115    NotMapped(Address),
116
117    // I have no idea whether the real TX-2 alarmed on writes to
118    // things that aren't really writeable (e.g. shaft encoder
119    // registers).  But by implemeting this we may be able to answer
120    // that question if some real (recovered) program writes to a
121    // location we assumed would be read-only.
122    ReadOnly(Address, ExtraBits),
123}
124
125impl Display for MemoryOpFailure {
126    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
127        match self {
128            MemoryOpFailure::NotMapped(addr) => {
129                write!(f, "address {addr:o} is not mapped to functioning memory")
130            }
131            MemoryOpFailure::ReadOnly(addr, _extra) => {
132                write!(f, "address {addr:o}is mapped to read-only memory")
133            }
134        }
135    }
136}
137
138impl error::Error for MemoryOpFailure {}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
141pub(crate) enum MetaBitChange {
142    None,
143    Set,
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
147pub(crate) enum BitChange {
148    Clear,
149    Set,
150    Flip,
151}
152
153#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
154pub(crate) struct WordChange {
155    pub(crate) bit: SkmBitSelector,
156    pub(crate) bitop: Option<BitChange>,
157    pub(crate) cycle: bool,
158}
159
160impl WordChange {
161    pub(crate) fn will_mutate_memory(&self) -> bool {
162        if self.cycle {
163            true
164        } else if self.bitop.is_none() {
165            false
166        } else {
167            // Only bit positions 1-9 (normal bits) and 10 (meta) are
168            // modifiable.
169            match &self.bit.bitpos {
170                SkmBitPos::Value(_) | SkmBitPos::Meta => true,
171                SkmBitPos::Nonexistent(_) | SkmBitPos::Parity | SkmBitPos::ParityCircuit => false,
172            }
173        }
174    }
175}
176
177pub(crate) trait MemoryMapped {
178    /// Fetch a word.
179    fn fetch(
180        &mut self,
181        ctx: &Context,
182        addr: &Address,
183        meta: &MetaBitChange,
184    ) -> Result<(Unsigned36Bit, ExtraBits), MemoryOpFailure>;
185
186    /// Store a word.
187    fn store(
188        &mut self,
189        ctx: &Context,
190        addr: &Address,
191        value: &Unsigned36Bit,
192        meta: &MetaBitChange,
193    ) -> Result<(), MemoryOpFailure>;
194
195    /// Mutate a bit in-place, returning its previous value.
196    fn change_bit(
197        &mut self,
198        ctx: &Context,
199        addr: &Address,
200        op: &WordChange,
201    ) -> Result<Option<bool>, MemoryOpFailure>;
202
203    /// Cycle a memory location one place to the left (as in TSD).
204    ///
205    /// This behaviour is described on page 4-9 of the [TX-2 Users
206    /// Handbook](https://tx-2.github.io/documentation#UH).
207    fn cycle_full_word_for_tsd(
208        &mut self,
209        ctx: &Context,
210        addr: &Address,
211    ) -> Result<ExtraBits, MemoryOpFailure>;
212}
213
214#[derive(Clone, Copy, Debug)]
215pub(crate) struct ExtraBits {
216    pub(crate) meta: bool,
217    // Notionally we could keep track of the parity bit here.
218    // However, in the absence of hardware failures there is no way to
219    // set it.  Therefore, we simply compute the expected value of the
220    // parity bit when we need it in change_word.
221}
222
223fn extra_bits_for_readonly_location() -> ExtraBits {
224    RESULT_OF_VMEMORY_UNKNOWN_READ.compute_extra_bits()
225}
226
227#[derive(Clone, Copy, Default)]
228pub(crate) struct MemoryWord {
229    // Fields are deliberately not public.
230    word: Unsigned36Bit,
231    meta: bool, // the metabit
232}
233
234impl MemoryWord {
235    fn compute_extra_bits(&self) -> ExtraBits {
236        ExtraBits { meta: self.meta }
237    }
238}
239
240impl Debug for MemoryWord {
241    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
242        write!(f, "{:>012o}", self.word)
243    }
244}
245
246mod mref {
247    //! The mref module exists to hide the internals of
248    //! [`MemoryWriteRef`] and [`MemoryReadRef`].
249    //!
250    //! We cannot simply use `&mut` [`MemoryWord`] because the
251    //! arithmetic element's registers A-E share a metabit (the
252    //! metabit of the M register).
253    //!
254    use super::{ExtraBits, MemoryWord};
255    use base::prelude::*;
256
257    pub(super) trait MemoryRead {
258        fn get_value(&self) -> Unsigned36Bit;
259        fn get_meta_bit(&self) -> bool;
260        /// Some memory fetches set the metabit of the fetched word.
261        fn set_meta_bit(&mut self);
262        fn extra_bits(&self) -> ExtraBits {
263            ExtraBits {
264                meta: self.get_meta_bit(),
265            }
266        }
267    }
268
269    /// `MemoryReadRef` logically represents a memory location
270    /// (register) which we can read.
271    pub(super) struct MemoryReadRef<'a> {
272        word: &'a Unsigned36Bit,
273        meta: &'a mut bool,
274    }
275
276    impl<'a> MemoryReadRef<'a> {
277        pub(super) fn new(word: &'a Unsigned36Bit, meta: &'a mut bool) -> MemoryReadRef<'a> {
278            MemoryReadRef { word, meta }
279        }
280
281        pub(super) fn readonly_from(mw: &'a mut MemoryWord) -> MemoryReadRef<'a> {
282            MemoryReadRef::new(&mw.word, &mut mw.meta)
283        }
284    }
285
286    impl MemoryRead for MemoryReadRef<'_> {
287        fn get_meta_bit(&self) -> bool {
288            *self.meta
289        }
290
291        fn set_meta_bit(&mut self) {
292            *self.meta = true;
293        }
294
295        fn get_value(&self) -> Unsigned36Bit {
296            *self.word
297        }
298    }
299
300    /// `MemoryWriteRef` logically represents a memory location
301    /// (register) which we can write to or read from.
302    pub(super) struct MemoryWriteRef<'a> {
303        word: &'a mut Unsigned36Bit,
304        meta: &'a mut bool,
305    }
306
307    impl<'a> MemoryWriteRef<'a> {
308        pub(super) fn new(word: &'a mut Unsigned36Bit, meta: &'a mut bool) -> MemoryWriteRef<'a> {
309            MemoryWriteRef { word, meta }
310        }
311    }
312
313    impl MemoryRead for MemoryWriteRef<'_> {
314        fn get_meta_bit(&self) -> bool {
315            *self.meta
316        }
317
318        fn set_meta_bit(&mut self) {
319            *self.meta = true;
320        }
321
322        fn get_value(&self) -> Unsigned36Bit {
323            *self.word
324        }
325    }
326
327    impl MemoryWriteRef<'_> {
328        pub(super) fn set_meta_bit_to_value(&mut self, value: bool) {
329            *self.meta = value;
330        }
331
332        pub(super) fn set_value(&mut self, value: Unsigned36Bit) {
333            *self.word = value;
334        }
335    }
336}
337
338#[cfg(test)]
339fn make_ctx() -> Context {
340    Context {
341        simulated_time: Duration::new(42, 42),
342        real_elapsed_time: Duration::new(7, 12),
343    }
344}
345#[derive(Debug)]
346struct Memory {
347    words: Vec<MemoryWord>,
348}
349
350impl Memory {
351    fn get_mut(&mut self, offset: usize) -> MemoryWriteRef<'_> {
352        let mw = &mut self.words[offset];
353        MemoryWriteRef::new(&mut mw.word, &mut mw.meta)
354    }
355
356    fn get(&mut self, offset: usize) -> MemoryReadRef<'_> {
357        let mw = &mut self.words[offset];
358        MemoryReadRef::new(&mw.word, &mut mw.meta)
359    }
360
361    fn new(size: usize) -> Memory {
362        let mut words = Vec::with_capacity(size);
363        while words.len() < size {
364            words.push(MemoryWord::default());
365        }
366        Memory { words }
367    }
368}
369
370#[derive(Debug)]
371pub struct MemoryUnit {
372    /// The S-memory is core memory with transistorized logic.
373    s_memory: Memory,
374    /// The T-memory is faster than S-memory, but smaller.
375    t_memory: Memory,
376    /// The U-memory was supposed to be like the T-memory, but was not
377    /// fitted.
378    u_memory: Option<Memory>,
379    /// The V-memory contains flip-flops (e.g. registers A-E, M),
380    /// memory-mapped hardware devices and the plugboards.
381    v_memory: VMemory,
382}
383
384enum MemoryDecode {
385    S(usize),
386    T(usize),
387    U(usize),
388    // The V-memory emulation is implemented in terms of absolute
389    // addresses, not in terms of offsets into the V-memory.  So there
390    // is no need for an offset field for the V-memory case.
391    V,
392}
393
394fn decode(address: &Address) -> Option<MemoryDecode> {
395    // MemoryDecode32 is a workaround for the fact that our address
396    // arithmetic uses u32 but we want to return an offset of type
397    // usize.
398    enum MemoryDecode32 {
399        S(u32),
400        T(u32),
401        U(u32),
402        V,
403    }
404    let addr: u32 = u32::from(address);
405    let decoded = {
406        if addr < T_MEMORY_START {
407            Some(MemoryDecode32::S(addr - S_MEMORY_START))
408        } else if addr < U_MEMORY_START {
409            Some(MemoryDecode32::T(addr - T_MEMORY_START))
410        } else if addr < (U_MEMORY_START + U_MEMORY_SIZE) {
411            Some(MemoryDecode32::U(addr - U_MEMORY_START))
412        } else if addr < V_MEMORY_START {
413            // This address is valid, but this memory region (after the U
414            // memory, before the V memory) is not mapped to anything.
415            None
416        } else if (V_MEMORY_START..V_MEMORY_START + V_MEMORY_SIZE).contains(&addr) {
417            Some(MemoryDecode32::V)
418        } else {
419            // The end of V memory is the highest address it is possible
420            // to form in 17 bits.  So, it should not be possible to form
421            // an invalid address in an Address struct, so we should not
422            // be able to get here.
423            unreachable!(
424                "Access to memory address {:?} should be impossible",
425                &address
426            );
427        }
428    };
429    // This code should not panic since the input Address type should
430    // not allow an address which is large enough that it can't be
431    // represented in a usize value.  The largest offset which the
432    // code above should compute is the last address of S-memory, and
433    // that fits into 16 bits.
434    decoded.map(|d| match d {
435        MemoryDecode32::S(addr) => MemoryDecode::S(addr.try_into().unwrap()),
436        MemoryDecode32::T(addr) => MemoryDecode::T(addr.try_into().unwrap()),
437        MemoryDecode32::U(addr) => MemoryDecode::U(addr.try_into().unwrap()),
438        MemoryDecode32::V => MemoryDecode::V,
439    })
440}
441
442/// `MemoryConfiguration` indicates how the emulated TX-2's memory is
443/// configured.
444pub struct MemoryConfiguration {
445    pub with_u_memory: bool,
446}
447
448impl MemoryUnit {
449    #[must_use]
450    pub fn new(ctx: &Context, config: &MemoryConfiguration) -> MemoryUnit {
451        fn u32_to_usize(n: u32) -> usize {
452            usize::try_from(n).expect("Only systems where u32 fits into usize are supported")
453        }
454        MemoryUnit {
455            s_memory: Memory::new(u32_to_usize(S_MEMORY_SIZE)),
456            t_memory: Memory::new(u32_to_usize(T_MEMORY_SIZE)),
457            u_memory: if config.with_u_memory {
458                Some(Memory::new(u32_to_usize(U_MEMORY_SIZE)))
459            } else {
460                None
461            },
462            v_memory: VMemory::new(ctx),
463        }
464    }
465
466    #[must_use]
467    pub fn get_a_register(&self) -> Unsigned36Bit {
468        self.v_memory.get_a_register()
469    }
470
471    #[must_use]
472    pub fn get_b_register(&self) -> Unsigned36Bit {
473        self.v_memory.get_b_register()
474    }
475
476    #[must_use]
477    pub fn get_c_register(&self) -> Unsigned36Bit {
478        self.v_memory.get_c_register()
479    }
480
481    #[must_use]
482    pub fn get_d_register(&self) -> Unsigned36Bit {
483        self.v_memory.get_d_register()
484    }
485
486    #[must_use]
487    pub fn get_e_register(&self) -> Unsigned36Bit {
488        self.v_memory.get_e_register()
489    }
490
491    pub fn set_a_register(&mut self, value: Unsigned36Bit) {
492        self.v_memory.set_a_register(value);
493    }
494
495    pub fn set_b_register(&mut self, value: Unsigned36Bit) {
496        self.v_memory.set_b_register(value);
497    }
498
499    pub fn set_c_register(&mut self, value: Unsigned36Bit) {
500        self.v_memory.set_c_register(value);
501    }
502
503    pub fn set_d_register(&mut self, value: Unsigned36Bit) {
504        self.v_memory.set_d_register(value);
505    }
506
507    pub fn set_e_register(&mut self, value: Unsigned36Bit) {
508        self.v_memory.set_e_register(value);
509    }
510
511    /// Perform a memory read access.  Return a [`MemoryReadRef`] for
512    /// the memory word being accessed.
513    fn read_access<'a>(
514        &'a mut self,
515        ctx: &Context,
516        addr: &Address,
517    ) -> Result<MemoryReadRef<'a>, MemoryOpFailure> {
518        match decode(addr) {
519            Some(MemoryDecode::S(offset)) => Ok(self.s_memory.get(offset)),
520            Some(MemoryDecode::T(offset)) => Ok(self.t_memory.get(offset)),
521            Some(MemoryDecode::U(offset)) => {
522                if let Some(u) = &mut self.u_memory {
523                    Ok(u.get(offset))
524                } else {
525                    Err(MemoryOpFailure::NotMapped(*addr))
526                }
527            }
528            Some(MemoryDecode::V) => self.v_memory.read_access(ctx, addr),
529            None => Err(MemoryOpFailure::NotMapped(*addr)),
530        }
531    }
532
533    /// Perform a memory write access.  Return a [`MemoryWriteRef`]
534    /// for the memory word being accessed.  If the memory location is
535    /// read-only, return `Ok(None)`.
536    fn write_access<'a>(
537        &'a mut self,
538        ctx: &Context,
539        addr: &Address,
540    ) -> Result<Option<MemoryWriteRef<'a>>, MemoryOpFailure> {
541        match decode(addr) {
542            Some(MemoryDecode::S(offset)) => Ok(Some(self.s_memory.get_mut(offset))),
543            Some(MemoryDecode::T(offset)) => Ok(Some(self.t_memory.get_mut(offset))),
544            Some(MemoryDecode::U(offset)) => {
545                if let Some(u) = &mut self.u_memory {
546                    Ok(Some(u.get_mut(offset)))
547                } else {
548                    Err(MemoryOpFailure::NotMapped(*addr))
549                }
550            }
551            Some(MemoryDecode::V) => self.v_memory.write_access(ctx, addr),
552            None => Err(MemoryOpFailure::NotMapped(*addr)),
553        }
554    }
555}
556
557/// Implement the heart of the [`MemoryMapped::change_bit()`]
558/// operation used by the SKM instruction.  Returns the value of the
559/// selected bit, or `None` if the bit selector indicates a
560/// nonexistent bit (e.g. 0.0, 1.0).
561fn skm_bitop_get<R: MemoryRead>(target_word: &R, op: &WordChange) -> Option<bool> {
562    // As the documentation for the SKM instruction (user
563    // handbook, page 3-35) explains, we perform the
564    // possible bit change before the possible rotate.
565    match op.bit.bitpos {
566        SkmBitPos::Value(value_bit_pos) => {
567            let mask: u64 = BitSelector {
568                quarter: op.bit.quarter,
569                bitpos: value_bit_pos,
570            }
571            .raw_mask();
572            Some((target_word.get_value() & mask) != 0)
573        }
574        SkmBitPos::Meta => {
575            // The metabit.
576            Some(target_word.get_meta_bit())
577        }
578        SkmBitPos::Parity | SkmBitPos::ParityCircuit => {
579            // But 11 is the parity bit, 12 is the computed parity
580            // (see SKM instruction description, page 3-34 of the
581            // Users Handbook).
582            //
583            // Both are read-only, but I don't think an attempt to
584            // modify them trips an alarm (at least, I can't see any
585            // mention of this in the SKM documentation).
586            Some(u64::from(target_word.get_value()).count_ones() & 1 != 0)
587        }
588        SkmBitPos::Nonexistent(_) => None,
589    }
590}
591
592fn skm_bitop_write(mut target: MemoryWriteRef<'_>, op: &WordChange) -> Option<bool> {
593    let maybe_bit: Option<bool> = skm_bitop_get(&target, op);
594
595    match op.bit.bitpos {
596        SkmBitPos::Parity | SkmBitPos::ParityCircuit | SkmBitPos::Nonexistent(_) => {
597            // Do nothing.  This matches the explicit expectations of
598            // the SKM instruction for bits 0, 11, 12 (TX-2 Users
599            // Handbook, page 3-34).
600            //
601            // We assume that for other nonexistent bits (13, 14, 15)
602            // the behavious should be the same as for bit 0.
603        }
604
605        SkmBitPos::Value(value_bit_pos) => {
606            let mask: Unsigned36Bit = BitSelector {
607                quarter: op.bit.quarter,
608                bitpos: value_bit_pos,
609            }
610            .mask();
611            match op.bitop {
612                None => (),
613                Some(BitChange::Clear) => target.set_value(target.get_value() & !mask),
614                Some(BitChange::Set) => target.set_value(target.get_value() | mask),
615                Some(BitChange::Flip) => target.set_value(target.get_value() ^ mask),
616            }
617        }
618        SkmBitPos::Meta => {
619            // The metabit.
620            match op.bitop {
621                None => (),
622                Some(BitChange::Clear) => target.set_meta_bit_to_value(false),
623                Some(BitChange::Set) => target.set_meta_bit_to_value(true),
624                Some(BitChange::Flip) => {
625                    let meta_bit_val: bool = target.get_meta_bit();
626                    target.set_meta_bit_to_value(!meta_bit_val);
627                }
628            }
629        }
630    }
631    if op.cycle {
632        target.set_value(target.get_value() >> 1);
633    }
634    maybe_bit
635}
636
637impl MemoryMapped for MemoryUnit {
638    fn fetch(
639        &mut self,
640        ctx: &Context,
641        addr: &Address,
642        side_effect: &MetaBitChange,
643    ) -> Result<(Unsigned36Bit, ExtraBits), MemoryOpFailure> {
644        match self.read_access(ctx, addr) {
645            Err(e) => Err(e),
646            Ok(mut mem_word) => {
647                let word = mem_word.get_value();
648                let extra_bits = mem_word.extra_bits();
649                match side_effect {
650                    MetaBitChange::None => (),
651                    MetaBitChange::Set => {
652                        let a32 = u32::from(addr);
653                        if a32 >= V_MEMORY_START {
654                            // The description of the SKM instruction doesn't state
655                            // explicitly that SKM works on V-memory, but since
656                            // arithmetic unit registers are mapped to it, it would
657                            // make sense.  However, there are clearly other locations
658                            // in V memory (e.g. the plugboard) that we can't cycle.
659                            if *side_effect != MetaBitChange::None {
660                                // Changng meta bits in V memory is not allowed,
661                                // see the longer comment in the store() method.
662                                return Err(MemoryOpFailure::ReadOnly(*addr, extra_bits));
663                            }
664                        } else {
665                            mem_word.set_meta_bit();
666                        }
667                    }
668                }
669                Ok((word, extra_bits))
670            }
671        }
672    }
673
674    fn store(
675        &mut self,
676        ctx: &Context,
677        addr: &Address,
678        value: &Unsigned36Bit,
679        meta: &MetaBitChange,
680    ) -> Result<(), MemoryOpFailure> {
681        let a32 = u32::from(addr);
682        if a32 >= V_MEMORY_START && matches!(meta, MetaBitChange::Set) {
683            // This is an attempt to set a meta bit in V memory.
684            //
685            // The meta bits of registers A..E cannot be
686            // set by the "set metabits of.." modes of IOS 42.
687            //
688            // According to page 5-23 of the User Handbook, attempts
689            // to set the metabit of registers via the MKC instruction
690            // actually set the meta bit of the M register.  But I
691            // don't know how that manifests to the programmer as an
692            // observable behaviour.
693            //
694            // For now we generate a failure in the hope that we will
695            // eventually find a program which performs this action,
696            // and study it to discover the actual behaviour of the
697            // TX-2 that the program expects.
698            //
699            // The User Handbook also states that V-memory locations
700            // other than registers A-E cannot be written at all.
701            return Ok(()); // ignore the write.
702        }
703
704        match self.write_access(ctx, addr) {
705            Err(e) => {
706                return Err(e);
707            }
708            Ok(None) => {
709                // Attempt to write to memory that cannot be written.
710                // We just ignore this.
711                return Ok(());
712            }
713            Ok(Some(mut mem_word)) => {
714                mem_word.set_value(*value);
715                match meta {
716                    MetaBitChange::None => (),
717                    MetaBitChange::Set => mem_word.set_meta_bit(),
718                }
719            }
720        }
721        Ok(())
722    }
723
724    /// Cycle the memory word for `TSD`.
725    ///
726    /// This behaviour is described on page 4-9 of the [TX-2 Users
727    /// Handbook](https://tx-2.github.io/documentation#UH).
728    fn cycle_full_word_for_tsd(
729        &mut self,
730        ctx: &Context,
731        addr: &Address,
732    ) -> Result<ExtraBits, MemoryOpFailure> {
733        match self.write_access(ctx, addr) {
734            Ok(Some(mut target)) => {
735                let extra_bits = target.extra_bits();
736                target.set_value(target.get_value() << 1);
737                Ok(extra_bits)
738            }
739            Err(e) => {
740                // The memory address is not mapped at all.
741                Err(e)
742            }
743            Ok(None) => {
744                event!(Level::DEBUG, "Cannot cycle read-only memory location");
745                Err(MemoryOpFailure::ReadOnly(
746                    *addr,
747                    extra_bits_for_readonly_location(),
748                ))
749            }
750        }
751    }
752
753    fn change_bit(
754        &mut self,
755        ctx: &Context,
756        addr: &Address,
757        op: &WordChange,
758    ) -> Result<Option<bool>, MemoryOpFailure> {
759        fn downgrade_op(op: &WordChange) -> WordChange {
760            WordChange {
761                bit: op.bit,  // access the same bit
762                bitop: None,  // read-only
763                cycle: false, // read-only
764            }
765        }
766
767        if op.will_mutate_memory() {
768            // If the memory address is not mapped at all, access will
769            // return Err, causing the next line to bail out of this
770            // function.
771            match self.write_access(ctx, addr)? {
772                None => {
773                    // The memory address is mapped to read-only
774                    // memory.  For example, plugboard memory.
775                    //
776                    // We downgrade the bit operation to be
777                    // non-mutating, so that the outcome of the bit
778                    // test is as it should be, but the memory-write
779                    // is inhibited.
780                    let mem_word = self.read_access(ctx, addr)?;
781                    let downgraded_op = downgrade_op(op);
782                    assert!(!downgraded_op.will_mutate_memory()); // should be read-only now
783                    Ok(skm_bitop_get(&mem_word, &downgraded_op))
784                }
785                Some(mem_word) => Ok(skm_bitop_write(mem_word, op)),
786            }
787        } else {
788            let mem_word = self.read_access(ctx, addr)?;
789            Ok(skm_bitop_get(&mem_word, op))
790        }
791    }
792}
793
794/// The TX-2's V-memory.
795///
796/// # Metabits
797///
798/// Arithmetic registers have no meta bit.  Accesses which attempt
799/// to read the meta bit of registers A, B, , D, E actually return
800/// the meta bit in the M register.  This is briefly described on
801/// page 5-23 of the User Handbook.
802///
803/// It says,
804///
805/// > The data reference metabit (`M⁴˙¹⁰`) can be detected only when
806/// > set (just as `N⁴˙¹⁰` above).  Note that it can be changed
807/// > without a memory reference for it serves as the metabit of the
808/// > A, B, C, D, and E registers. (i.e., `MKC₄.₁₀ A` or `MKC₄.₁₀ B`
809/// > will change bit 4.10 of M)."
810///
811/// V memory in general does behave as if it has a meta bit.  For
812/// example, there is a push-button on the console that acts as the
813/// value of the meta bit of the shaft encoder register.
814///
815/// See also <https://github.com/TX-2/TX-2-simulator/issues/59>.
816#[derive(Debug)]
817struct VMemory {
818    a_register: Unsigned36Bit,
819    b_register: Unsigned36Bit,
820    c_register: Unsigned36Bit,
821    d_register: Unsigned36Bit,
822    e_register: Unsigned36Bit,
823    m_register_metabit: bool,
824
825    unimplemented_shaft_encoder: MemoryWord,
826    unimplemented_external_input_register: MemoryWord,
827    rtc: MemoryWord,
828    rtc_start: Duration,
829    codabo_start_point: [Unsigned36Bit; 8],
830    plugboard: [Unsigned36Bit; 32],
831
832    /// Writes to unknown locations are required to be ignored, but
833    /// reads have to return a value.  If `permit_unknown`_reads is
834    /// set, a special value is returned.  If not, a QSAL alarm will
835    /// be raised (though that alarm may in turn be suppressed).
836    permit_unknown_reads: bool,
837    sacrificial_word_for_unknown_reads: MemoryWord,
838
839    // Writes to VMemory metabits are civerted to here so that setting
840    // the metabit has no (permanent) effect.
841    sacrificial_metabit: bool,
842}
843
844/// This is taken from the listing in section 5-5.2 (page 5-27) of
845/// the Users Handbook.
846const fn standard_plugboard_internal() -> [Unsigned36Bit; 32] {
847    // This data has not yet been double-checked and no tests
848    // validate it, so it might be quite wrong.
849    //
850    [
851        // Plugboard memory starts with Plugboard B at 0o3777740.
852        //
853        // F-memory settings; these are verified against the
854        // information from Table 7-2 by a test in the exchanger code.
855        u36!(0o_760342_340000),
856        u36!(0o_410763_762761),
857        u36!(0o_160142_140411),
858        u36!(0o_202163_162161),
859        u36!(0o_732232_230200),
860        u36!(0o_605731_730733),
861        u36!(0o_320670_750600),
862        u36!(0o_604331_330333),
863        // 0o377750: standard program to load the F-memory settings
864        // (this is not verified by the test in the exchanger code).
865        u36!(0o_002200_377740), // ⁰⁰SPG 377740
866        u36!(0o_042200_377741), // ⁰⁴SPG 377741
867        u36!(0o_102200_377742), // ¹⁰SPG 277742
868        u36!(0o_142200_377743), // ¹⁴SPG 277743
869        u36!(0o_202200_377744), // ²⁰SPG 277744
870        u36!(0o_242200_377745), // ²⁴SPG 277745
871        u36!(0o_302200_377746), // ³⁰SPG 277746
872        u36!(0o_342200_377747), // ³⁴SPG 277747
873        // Plugboard A, 0o377760-0o377777
874        // 0o0377760: Standard program: read in reader leader from paper tape
875        //
876        // This code uses 3 index registers:
877        // X₅₂: start address for sequence 52, for PETR (paper tape) device
878        // X₅₃: counts the number of TSD operations needed to fill a word
879        // X₅₄: starts at -23, counts upward; we add this to 26 to get the
880        //      address into which we perform tape transfers (with TSD),
881        //      so that the standard reader leader is read into locations 3 to 26
882        u36!(0o011254_000023), // ¹SKX₅₄ 23        ** X₅₄=-23, length of reader leader
883        u36!(0o001252_377763), // REX₅₂ 377763     ** Load 377763 into X₅₂ (seq 52 start point)
884        u36!(0o210452_030106), // ²¹IOS₅₂ 30106    ** PETR: Load bin, read assembly mode
885        // 0o0377763
886        u36!(0o001253_000005), // REX₅₃ 5          ** Load 5 into X₅₃
887        // 0o0377764
888        u36!(0o405754_000026), // h TSD₅₄ 26       ** Load into 26+X₅₄ (which is negative)
889        // 0o0377765
890        u36!(0o760653_377764), // h ³⁶JPX₅₃ 377764 ** loop if X₅₃>0, decrement it
891        // 0o0377766
892        u36!(0o410754_377763), // h ¹JNX₅₄ 377763  ** loop if X₅₄<0, increment it
893        // 0o0377767
894        u36!(0o140500_000003), // ¹⁴JPQ 3          ** Jump to start of reader leader
895        // At the time we jump, sequence 0o52 is executing, with
896        // X₅₂ = 0o377763, X₅₃ = 0, X₅₄ = 0.
897        //
898        // 0o0377770: Standard program: clear memory
899        u36!(0o001277_207777),
900        u36!(0o001677_777776),
901        u36!(0o140500_377773),
902        u36!(0o001200_777610),
903        u36!(0o760677_377771),
904        u36!(0o301712_377744),
905        u36!(0o000077_000000),
906        u36!(0o140500_377750),
907    ]
908}
909
910pub(crate) fn get_standard_plugboard() -> Vec<Unsigned36Bit> {
911    standard_plugboard_internal().to_vec()
912}
913
914const RESULT_OF_VMEMORY_UNKNOWN_READ: MemoryWord = MemoryWord {
915    word: u36!(0o404_404_404_404),
916    meta: false,
917};
918
919impl VMemory {
920    fn new(ctx: &Context) -> VMemory {
921        let mut result = VMemory {
922            a_register: Unsigned36Bit::default(),
923            b_register: Unsigned36Bit::default(),
924            c_register: Unsigned36Bit::default(),
925            d_register: Unsigned36Bit::default(),
926            e_register: Unsigned36Bit::default(),
927            m_register_metabit: false,
928            codabo_start_point: [
929                Unsigned36Bit::default(),
930                Unsigned36Bit::default(),
931                Unsigned36Bit::default(),
932                Unsigned36Bit::default(),
933                Unsigned36Bit::default(),
934                Unsigned36Bit::default(),
935                Unsigned36Bit::default(),
936                Unsigned36Bit::default(),
937            ],
938            plugboard: standard_plugboard_internal(),
939            unimplemented_shaft_encoder: MemoryWord::default(),
940            unimplemented_external_input_register: MemoryWord::default(),
941            rtc: MemoryWord::default(),
942            rtc_start: ctx.real_elapsed_time,
943            permit_unknown_reads: true,
944            sacrificial_word_for_unknown_reads: RESULT_OF_VMEMORY_UNKNOWN_READ,
945            sacrificial_metabit: false,
946        };
947        result.reset_rtc(ctx);
948        result
949    }
950
951    fn get_a_register(&self) -> Unsigned36Bit {
952        self.a_register
953    }
954
955    fn get_b_register(&self) -> Unsigned36Bit {
956        self.b_register
957    }
958
959    fn get_c_register(&self) -> Unsigned36Bit {
960        self.c_register
961    }
962
963    fn get_d_register(&self) -> Unsigned36Bit {
964        self.d_register
965    }
966
967    fn get_e_register(&self) -> Unsigned36Bit {
968        self.e_register
969    }
970
971    fn set_a_register(&mut self, value: Unsigned36Bit) {
972        self.a_register = value;
973    }
974
975    fn set_b_register(&mut self, value: Unsigned36Bit) {
976        self.b_register = value;
977    }
978
979    fn set_c_register(&mut self, value: Unsigned36Bit) {
980        self.c_register = value;
981    }
982
983    fn set_d_register(&mut self, value: Unsigned36Bit) {
984        self.d_register = value;
985    }
986
987    fn set_e_register(&mut self, value: Unsigned36Bit) {
988        self.e_register = value;
989    }
990
991    /// Perform a memory read.
992    fn read_access<'a>(
993        &'a mut self,
994        ctx: &Context,
995        addr: &Address,
996    ) -> Result<MemoryReadRef<'a>, MemoryOpFailure> {
997        // Most locations in V memory have a metabit which cannot be
998        // set in software.  In some cases (e.g. the shaft encoders)
999        // it can be set in hardware.
1000        let readonly = |word: &'a Unsigned36Bit, meta_value: bool, meta_loc: &'a mut bool| {
1001            *meta_loc = meta_value;
1002            MemoryReadRef::new(word, meta_loc)
1003        };
1004
1005        match u32::from(addr) {
1006            // The metabit of the Arithmetic Element registers is
1007            // shared (in fact, it is the metabit of the M register).
1008            0o0377604 => Ok(MemoryReadRef::new(
1009                &self.a_register,
1010                &mut self.m_register_metabit,
1011            )),
1012            0o0377605 => Ok(MemoryReadRef::new(
1013                &self.b_register,
1014                &mut self.m_register_metabit,
1015            )),
1016            0o0377606 => Ok(MemoryReadRef::new(
1017                &self.c_register,
1018                &mut self.m_register_metabit,
1019            )),
1020            0o0377607 => Ok(MemoryReadRef::new(
1021                &self.d_register,
1022                &mut self.m_register_metabit,
1023            )),
1024            0o0377610 => Ok(MemoryReadRef::new(
1025                &self.e_register,
1026                &mut self.m_register_metabit,
1027            )),
1028            0o0377620 => {
1029                event!(
1030                    Level::WARN,
1031                    "Reading the shaft encoder is not yet implemented"
1032                );
1033                Ok(MemoryReadRef::readonly_from(
1034                    &mut self.unimplemented_shaft_encoder,
1035                ))
1036            }
1037            0o0377621 => {
1038                event!(
1039                    Level::WARN,
1040                    "Reading the external input register is not yet implemented"
1041                );
1042                Ok(readonly(
1043                    &self.unimplemented_external_input_register.word,
1044                    self.unimplemented_external_input_register.meta,
1045                    &mut self.sacrificial_metabit,
1046                ))
1047            }
1048            0o0377630 => {
1049                self.update_rtc(ctx);
1050                Ok(MemoryReadRef::readonly_from(&mut self.rtc))
1051            }
1052            0o0377710 => Ok(readonly(
1053                &self.codabo_start_point[0],
1054                false,
1055                &mut self.sacrificial_metabit,
1056            )), // CODABO Reset0
1057            0o0377711 => Ok(readonly(
1058                &mut self.codabo_start_point[1],
1059                false,
1060                &mut self.sacrificial_metabit,
1061            )), // CODABO Reset1
1062            0o0377712 => Ok(readonly(
1063                &mut self.codabo_start_point[2],
1064                false,
1065                &mut self.sacrificial_metabit,
1066            )), // CODABO Reset2
1067            0o0377713 => Ok(readonly(
1068                &mut self.codabo_start_point[3],
1069                false,
1070                &mut self.sacrificial_metabit,
1071            )), // CODABO Reset3
1072            0o0377714 => Ok(readonly(
1073                &mut self.codabo_start_point[4],
1074                false,
1075                &mut self.sacrificial_metabit,
1076            )), // CODABO Reset4
1077            0o0377715 => Ok(readonly(
1078                &mut self.codabo_start_point[5],
1079                false,
1080                &mut self.sacrificial_metabit,
1081            )), // CODABO Reset5
1082            0o0377716 => Ok(readonly(
1083                &mut self.codabo_start_point[6],
1084                false,
1085                &mut self.sacrificial_metabit,
1086            )), // CODABO Reset6
1087            0o0377717 => Ok(readonly(
1088                &mut self.codabo_start_point[7],
1089                false,
1090                &mut self.sacrificial_metabit,
1091            )), // CODABO Reset7
1092
1093            a @ 0o0377740..=0o0377777 => {
1094                if let Ok(offset) = TryInto::<usize>::try_into(a - 0o0377740) {
1095                    Ok(readonly(
1096                        &mut self.plugboard[offset],
1097                        false,
1098                        &mut self.sacrificial_metabit,
1099                    ))
1100                } else {
1101                    // Unreachable because the matched range is
1102                    // not large enough to exceed the capacity of
1103                    // usize (which we assume is at least 2^16).
1104                    unreachable!()
1105                }
1106            }
1107            _ => {
1108                if self.permit_unknown_reads {
1109                    Ok(MemoryReadRef::readonly_from(
1110                        &mut self.sacrificial_word_for_unknown_reads,
1111                    ))
1112                } else {
1113                    event!(
1114                        Level::ERROR,
1115                        "V-memory read of unknown location {:o} is not allowed",
1116                        addr
1117                    );
1118                    Err(MemoryOpFailure::NotMapped(*addr))
1119                }
1120            }
1121        }
1122    }
1123
1124    /// Perform a memory write.  Return a mutable reference to the
1125    /// memory word being accessed or, if this is an attempt to write
1126    /// to a read-only location, return `None`.
1127    fn write_access<'a>(
1128        &'a mut self,
1129        _ctx: &Context,
1130        addr: &Address,
1131    ) -> Result<Option<MemoryWriteRef<'a>>, MemoryOpFailure> {
1132        // The AE registers are supposed to share a single metabit, the metabit of the M register.
1133        match u32::from(addr) {
1134            0o0377604 => Ok(Some(MemoryWriteRef::new(
1135                &mut self.a_register,
1136                &mut self.m_register_metabit,
1137            ))),
1138            0o0377605 => Ok(Some(MemoryWriteRef::new(
1139                &mut self.b_register,
1140                &mut self.m_register_metabit,
1141            ))),
1142            0o0377606 => Ok(Some(MemoryWriteRef::new(
1143                &mut self.c_register,
1144                &mut self.m_register_metabit,
1145            ))),
1146            0o0377607 => Ok(Some(MemoryWriteRef::new(
1147                &mut self.d_register,
1148                &mut self.m_register_metabit,
1149            ))),
1150            0o0377610 => Ok(Some(MemoryWriteRef::new(
1151                &mut self.e_register,
1152                &mut self.m_register_metabit,
1153            ))),
1154            // Example 10 on page 3-17 of the Users Handbook says "V
1155            // memory, except the A, B, C, D, and E registers cannot
1156            // be changed by any instruction".
1157            _ => Ok(None),
1158        }
1159    }
1160
1161    fn reset_rtc(&mut self, ctx: &Context) {
1162        self.rtc_start = ctx.real_elapsed_time;
1163        self.rtc.word = Unsigned36Bit::ZERO;
1164    }
1165
1166    fn update_rtc(&mut self, ctx: &Context) {
1167        const RTC_MODULUS: u128 = 1 << 36;
1168
1169        if ctx.real_elapsed_time >= self.rtc_start {
1170            // Page 5-19 of the Nov 1963 Users Handbook states that the
1171            // RTC has a period of 10 microseconds and will reset itself
1172            // "every 7.6 days or so".
1173            //
1174            // These facts seems to be inconsistent, since 2^36 *
1175            // 10 microseconds is 7.953643140740741 days (assuming
1176            // 86400 seconds per day).  In other words, this
1177            // period is about 5% too high, and is an odd choice
1178            // of rounding (when the author could have said "just
1179            // under 8 days" or "just over 7.9 days").
1180            //
1181            // A clock period of 9.555 microseconds on the other
1182            // hand would give a rollover period of 7.5997 days
1183            // (about 7d 14h 23m 34.1s).
1184            //
1185            // Since the tick interval is more important than the
1186            // time between rollovers though, we stick with 10
1187            // microseconds.
1188            const TICK_MICROSEC: u128 = 10;
1189            let duration = ctx.real_elapsed_time - self.rtc_start;
1190            let tick_count = duration.as_micros() / TICK_MICROSEC;
1191            assert!(u128::from(u64::from(Unsigned36Bit::MAX)) < RTC_MODULUS);
1192            match u64::try_from(tick_count % RTC_MODULUS) {
1193                Ok(n) => match Unsigned36Bit::try_from(n) {
1194                    Ok(n) => {
1195                        self.rtc.word = n;
1196                    }
1197                    Err(_) => {
1198                        // (x % RTC_MODULUS) <= Unsigned36Bit::MAX for
1199                        // all x, so this case cannot occur.
1200                        unreachable!();
1201                    }
1202                },
1203                Err(_) => {
1204                    // (x % RTC_MODULUS) <= Unsigned36Bit::MAX for
1205                    // all x, so this case cannot occur.
1206                    unreachable!();
1207                }
1208            }
1209        } else {
1210            // There has been a correction to the system clock used
1211            // to generate `ctx.real_elapsed_time`.  We handle
1212            // this by pretending that the user has just pressed
1213            // the "reset RTC" button.
1214            self.reset_rtc(ctx);
1215        }
1216    }
1217}
1218
1219#[cfg(test)]
1220fn all_physical_memory_addresses() -> RangeInclusive<u32> {
1221    let start = 0_u32;
1222    let end: u32 = u32::from(Unsigned18Bit::MAX) >> 1;
1223    start..=end
1224}
1225
1226#[test]
1227fn test_write_all_mem() {
1228    let context = make_ctx();
1229    let mut mem = MemoryUnit::new(
1230        &context,
1231        &MemoryConfiguration {
1232            with_u_memory: false,
1233        },
1234    );
1235    for a in all_physical_memory_addresses() {
1236        let addr: Address = Address::try_from(a).unwrap();
1237        // The point of this test is to verify that we con't have any
1238        // todo!()s in reachable code paths.
1239        let result = mem.write_access(&context, &addr);
1240        match result {
1241            Ok(Some(_)) => (),
1242            Ok(None) => {
1243                // The only memory for which writes are forbidden is
1244                // in V memory.
1245                assert!(a >= V_MEMORY_START);
1246                assert!(a <= 0o0377777);
1247                // However, writes to the arithmetic element are
1248                // permitted.
1249                assert_ne!(a, 0o0377604); // A
1250                assert_ne!(a, 0o0377605); // B
1251                assert_ne!(a, 0o0377606); // C
1252                assert_ne!(a, 0o0377607); // D
1253                assert_ne!(a, 0o0377610); // E
1254            }
1255            Err(MemoryOpFailure::NotMapped(_)) => (),
1256            Err(e) => {
1257                panic!("Failure {e:?} during write of memory address {addr:o}");
1258            }
1259        }
1260    }
1261}
1262
1263#[test]
1264fn test_read_all_mem() {
1265    #[cfg(test)]
1266    let context = make_ctx();
1267    let mut mem = MemoryUnit::new(
1268        &context,
1269        &MemoryConfiguration {
1270            with_u_memory: false,
1271        },
1272    );
1273    for a in all_physical_memory_addresses() {
1274        let addr: Address = Address::try_from(a).unwrap();
1275        // The point of this test is to verify that we con't have any
1276        // todo!()s in reachable code paths.
1277        let result = mem.read_access(&context, &addr);
1278        match result {
1279            Ok(_) => (),
1280            Err(MemoryOpFailure::NotMapped(_)) => (),
1281            Err(e) => {
1282                panic!("Failure {e:?} during read of memory address {addr:o}");
1283            }
1284        }
1285    }
1286}
1287
1288#[cfg(test)]
1289fn set_metabit(context: &Context, mem: &mut MemoryUnit, addr: Address, value: bool) {
1290    match mem.write_access(context, &addr) {
1291        Ok(Some(mut word)) => word.set_meta_bit_to_value(value),
1292        Ok(None) => {
1293            panic!("AE register at {addr:o} is not mapped");
1294        }
1295        Err(e) => {
1296            panic!("failed to write memory at {addr:o}: {e}");
1297        }
1298    }
1299}
1300
1301#[cfg(test)]
1302fn get_metabit(context: &Context, mem: &mut MemoryUnit, addr: Address) -> bool {
1303    match mem.read_access(context, &addr) {
1304        Ok(word) => word.get_meta_bit(),
1305        Err(e) => {
1306            panic!("failed to read memory at {addr:o}: {e}");
1307        }
1308    }
1309}
1310
1311#[test]
1312fn test_ae_registers_share_metabit() {
1313    // Registers ABCDE share a single metabit (the metabit of the M
1314    // register, in fact) and so we should be able to set (or clear)
1315    // the metabit of one of them and read it back through another.
1316    let context = make_ctx();
1317    let mut mem = MemoryUnit::new(
1318        &context,
1319        &MemoryConfiguration {
1320            with_u_memory: false,
1321        },
1322    );
1323    let a_addr: Address = Address::from(u18!(0o0377604));
1324    let b_addr: Address = Address::from(u18!(0o0377605));
1325    let c_addr: Address = Address::from(u18!(0o0377606));
1326    let d_addr: Address = Address::from(u18!(0o0377607));
1327    let e_addr: Address = Address::from(u18!(0o0377610));
1328    let ae_regs = [a_addr, b_addr, c_addr, d_addr, e_addr];
1329
1330    for first in 0..ae_regs.len() {
1331        for second in 0..ae_regs.len() {
1332            // Set the metabit at the first location.
1333            set_metabit(&context, &mut mem, ae_regs[first], true);
1334            // Verify that the metabit at the second location is set (1).
1335            assert!(get_metabit(&context, &mut mem, ae_regs[second]));
1336            // Clear the metabit at the second location.
1337            set_metabit(&context, &mut mem, ae_regs[second], false);
1338            // Verify that the metabit at the first location is clear (0).
1339            assert!(!get_metabit(&context, &mut mem, ae_regs[first]));
1340        }
1341    }
1342}