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}