cpu/
exchanger.rs

1//! The Exchange Element governs data exchange between memory (via the
2//! M register) and other parts of the central compute unit (including
3//! the arithmetic element) via the E register.
4//!
5//! The way in which the exchange element behaves is governed by the
6//! "System Configuration" which is a 9-bit value loaded from the
7//! F-memory.  Which specific value is loaded from the F-memory is
8//! determined by the 5-bit configuration field within the current
9//! instruction; the instruction is loaded into the N register).
10//!
11//! The standard set-up of the F-memory is described in Table 7-2 of
12//! the User Guide.
13use std::fmt::{self, Binary, Display, Formatter, Octal};
14
15use tracing::{Level, event};
16
17use base::prelude::*;
18
19use super::memory::get_standard_plugboard;
20
21/// The Exchange Element behaves differently in the M->E (i.e. load)
22/// direction and the E->M (i.e. store) direction.  This enumeration
23/// specifies the direction of the current transfer.
24pub(crate) enum ExchangeDirection {
25    /// Memory (M register) to AE (E register)
26    ME,
27    /// AE (E register) to Memory (M register)
28    EM,
29}
30
31// QuarterActivity has a 1 where a quarter is active (unlike the sense
32// in the configuration values, which are 0 for active).  Quarters in
33// QuarterActivity are numbered from 0.
34#[derive(Clone, Copy, Debug)]
35pub(crate) struct QuarterActivity(u8);
36
37impl QuarterActivity {
38    pub(crate) fn new(bits: u8) -> QuarterActivity {
39        assert_eq!(bits & !0b1111, 0);
40        QuarterActivity(bits)
41    }
42
43    pub(crate) fn is_active(&self, q: &u8) -> bool {
44        assert!(*q < 4, "invalid quarter {q}");
45        let mask = 1 << *q;
46        self.0 & mask != 0
47    }
48
49    pub(crate) fn first_active_quarter(&self) -> Option<u8> {
50        let n = self.0.trailing_zeros() as u8;
51        if n < 4 { Some(n) } else { None }
52    }
53
54    pub(crate) fn masked_by(&self, mask: u8) -> QuarterActivity {
55        QuarterActivity::new(self.0 & mask)
56    }
57}
58
59impl Binary for QuarterActivity {
60    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
61        write!(f, "{:b}", self.0)
62    }
63}
64
65#[test]
66fn test_quarteractivity_first_active_quarter() {
67    fn first_active_quarter(n: u8) -> Option<u8> {
68        QuarterActivity(n).first_active_quarter()
69    }
70    assert_eq!(first_active_quarter(0), None);
71    assert_eq!(first_active_quarter(0b0001), Some(0));
72    assert_eq!(first_active_quarter(0b0011), Some(0));
73    assert_eq!(first_active_quarter(0b1111), Some(0));
74    assert_eq!(first_active_quarter(0b1110), Some(1));
75    assert_eq!(first_active_quarter(0b0010), Some(1));
76    assert_eq!(first_active_quarter(0b1100), Some(2));
77    assert_eq!(first_active_quarter(0b0100), Some(2));
78    assert_eq!(first_active_quarter(0b1000), Some(3));
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq)]
82enum Permutation {
83    P0 = 0,
84    P1,
85    P2,
86    P3,
87    P4,
88    P5,
89    P6,
90    P7,
91}
92
93#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94pub(crate) enum SubwordForm {
95    FullWord = 0, // 36
96    Halves = 1,   // 18, 18
97    ThreeOne = 2, // 27, 9
98    Quarters = 3, //
99}
100
101/// The `SystemConfiguration` value specifies a global state of the
102/// computer which determines how the Arithmetic Element (in modern
103/// wording, arithmetic unit) communicates with memory.  The basic
104/// outline is given in Fig. 9 (page 20) of "A Functional Description
105/// of the Lincoln TX-2 Computer" by John M. Frankovitch and H. Philip
106/// Peterson.  A more complete description (including the
107/// corresponding F-memory values) is given in tables 7-2 and 7-2A of
108/// the TX-2 User's Handbook (pp 192-193 in my PDF copy).
109///
110/// The system configuration is a 9-bit value.  While Frankovitch and
111/// Peterson describe most significant bit as being spare, table 7-2
112/// appears to use it.
113///
114/// Figure 12-39 ("Configuration Block Diagram") (page 250) in Volume
115/// 2 of the TX-2 Technical manual describes how a word from the CF
116/// memory (a `QKIRcf` value) is decoded into permutation, activity
117/// (which quarters are active) and fracture (which quarters are
118/// considered to be separate).
119#[derive(Clone, Copy, Debug)]
120pub(crate) struct SystemConfiguration(Unsigned9Bit);
121
122impl From<u8> for SystemConfiguration {
123    fn from(n: u8) -> SystemConfiguration {
124        SystemConfiguration(Unsigned9Bit::from(n))
125    }
126}
127
128impl TryFrom<u16> for SystemConfiguration {
129    type Error = ConversionFailed;
130    fn try_from(n: u16) -> Result<SystemConfiguration, ConversionFailed> {
131        Unsigned9Bit::try_from(n).map(SystemConfiguration::from)
132    }
133}
134
135impl From<Unsigned9Bit> for SystemConfiguration {
136    fn from(n: Unsigned9Bit) -> SystemConfiguration {
137        SystemConfiguration(n)
138    }
139}
140
141impl From<SystemConfiguration> for Unsigned9Bit {
142    fn from(cfg: SystemConfiguration) -> Unsigned9Bit {
143        cfg.0
144    }
145}
146
147impl PartialEq for SystemConfiguration {
148    fn eq(&self, other: &SystemConfiguration) -> bool {
149        self.0.eq(&other.0)
150    }
151}
152
153impl Display for SystemConfiguration {
154    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
155        Octal::fmt(&self.0, f)
156    }
157}
158
159impl Octal for SystemConfiguration {
160    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
161        Octal::fmt(&self.0, f)
162    }
163}
164
165impl SystemConfiguration {
166    pub(crate) fn zero() -> SystemConfiguration {
167        SystemConfiguration(Unsigned9Bit::ZERO)
168    }
169
170    fn permutation(&self) -> Permutation {
171        match u16::from(self.0) & 0b111 {
172            0 => Permutation::P0,
173            1 => Permutation::P1,
174            2 => Permutation::P2,
175            3 => Permutation::P3,
176            4 => Permutation::P4,
177            5 => Permutation::P5,
178            6 => Permutation::P6,
179            7 => Permutation::P7,
180            _ => unreachable!(),
181        }
182    }
183
184    /// Extract a `QuarterActivity` value from the activity field of a
185    /// system configuration value.
186    ///
187    /// Active quarters are signaled by a 0 in the appropriate bit
188    /// position of the system configuration value.  A 1 signals that
189    /// the corresponding quarter is inactive.
190    ///
191    /// The mapping between configuration values and which quarter is
192    /// active is given in Table 12-4 in the technical manual (volume
193    /// 2, page 12-22).
194    ///
195    pub(crate) fn active_quarters(&self) -> QuarterActivity {
196        // CF7 CF6 CF5 CF4
197        //   x   x   x   0 => ACT1
198        //   x   x   0   x => ACT2
199        //   x   0   x   x => ACT3
200        //   0   x   x   x => ACT4
201        //
202        // These bit values are not mutually exclusive, so for example
203        // 0010 means that quarters 4, 3 and 1 are active.
204        let act_field: u16 = (!(u16::from(self.0) >> 3)) & 0b1111;
205        let result = QuarterActivity::new(act_field as u8);
206        event!(
207            Level::TRACE,
208            "active_quarters: system configuration {:>03o} -> result {:?}",
209            self,
210            result
211        );
212        result
213    }
214
215    fn subword_form(&self) -> SubwordForm {
216        const MASK: u16 = 0o3 << 7;
217        match u16::from(self.0) & MASK {
218            0b000000000 => SubwordForm::FullWord, // 36
219            0b010000000 => SubwordForm::Halves,   // 18,18
220            0b100000000 => SubwordForm::ThreeOne, // 27,9
221            0b110000000 => SubwordForm::Quarters, // 9,9,9,9
222            _ => unreachable!(),
223        }
224    }
225}
226
227#[cfg(test)]
228macro_rules! assert_octal_eq {
229    ($left:expr_2021, $right:expr_2021 $(,)?) => {{
230        match (&$left, &$right) {
231            (left_val, right_val) => {
232                if !(*left_val == *right_val) {
233                    panic!(
234                        "Assertion failed: {:#>012o} != {:>#012o}",
235                        left_val, right_val
236                    );
237                }
238            }
239        }
240    }};
241}
242
243/// Compute the quarter number (starting at 0) of the source word from
244/// which the values for `target_quarter` (also starting at 0) would
245/// be taken if it were active.
246///
247/// The numbering is for the load direction (i.e. data is travelling
248/// _downwards_ in the standard permutation diagram in table 7-2 of
249/// the Users Handbook).
250///
251/// Permutation behaviour is described in Volume 2 of the Technnical
252/// Manual.  In particular see section 12-6.4 and figure 12-45 (page
253/// 12-75), and figures 13-12 and 13-13 (pages 13-24 and 13-25).
254fn permutation_source(
255    permutation: &Permutation,
256    direction: &ExchangeDirection,
257    target_quarter: u8,
258) -> u8 {
259    match permutation {
260        Permutation::P0 => target_quarter % 4,
261        Permutation::P1 => match *direction {
262            ExchangeDirection::ME => (target_quarter + 1) % 4,
263            ExchangeDirection::EM => (target_quarter + 3) % 4,
264        },
265        Permutation::P2 => (target_quarter + 2) % 4,
266        Permutation::P3 => match *direction {
267            ExchangeDirection::ME => (target_quarter + 3) % 4,
268            ExchangeDirection::EM => (target_quarter + 1) % 4,
269        },
270        Permutation::P4 => target_quarter ^ 0b01,
271        Permutation::P5 => target_quarter ^ 0b11,
272        Permutation::P6 => match (direction, target_quarter) {
273            (&ExchangeDirection::ME, 3) => 2,
274            (&ExchangeDirection::ME, 2) => 1,
275            (&ExchangeDirection::ME, 1) => 3,
276            (&ExchangeDirection::ME, 0) => 0,
277            (&ExchangeDirection::EM, 3) => 1,
278            (&ExchangeDirection::EM, 2) => 3,
279            (&ExchangeDirection::EM, 1) => 2,
280            (&ExchangeDirection::EM, 0) => 0,
281            (_, _) => unreachable!(),
282        },
283        Permutation::P7 => match (direction, target_quarter) {
284            (&ExchangeDirection::ME, 3) => 1,
285            (&ExchangeDirection::ME, 2) => 3,
286            (&ExchangeDirection::ME, 1) => 2,
287            (&ExchangeDirection::ME, 0) => 0,
288            (&ExchangeDirection::EM, 3) => 2,
289            (&ExchangeDirection::EM, 2) => 1,
290            (&ExchangeDirection::EM, 1) => 3,
291            (&ExchangeDirection::EM, 0) => 0,
292            (_, _) => unreachable!(),
293        },
294    }
295}
296
297// TODO: add a unit test for sign extension in partially-active
298// subwords, based on the example in Figures 12-41, 12-42 (technical
299// manual, volume 2).
300
301fn quarter_mask(n: u8) -> u64 {
302    assert!(n < 4);
303    0o777 << (n * 9)
304}
305
306fn apply_sign(word: u64, quarter_number_from: u8, quarter_number_to: u8) -> u64 {
307    let signbit = word & (0o400 << (quarter_number_from * 9));
308    let mask = quarter_mask(quarter_number_to);
309    if signbit == 0 {
310        word & !mask
311    } else {
312        word | mask
313    }
314}
315
316fn sign_extend_quarters(
317    w: Unsigned36Bit,
318    activity: QuarterActivity,
319    ordering: &[u8],
320) -> Unsigned36Bit {
321    let mut word: u64 = u64::from(w);
322    if let Some(mut last_active) = activity.first_active_quarter() {
323        for q in ordering {
324            if activity.is_active(q) {
325                last_active = *q;
326            } else {
327                word = apply_sign(word, last_active, *q);
328            }
329        }
330        Unsigned36Bit::try_from(word).expect("result should be in range (this is a bug)")
331    } else {
332        w
333    }
334}
335
336/// Perform sign extension.  From User Handbook, figure 13-14, "The
337/// sign bit of an active quarter of a partially-active subword is
338/// extended to the left until an active quarter is again met, this
339/// must be interpreted in terms of the possible partially active
340/// subwords".  I don't know what the second part of this sentence
341/// means.  The accompanying diagram shows the sign bit being extended
342/// into quarters that are to the right of an active quarter (in order
343/// words the leftward extension wraps after it hits q4).
344pub(crate) fn sign_extend(
345    form: &SubwordForm,
346    word: Unsigned36Bit,
347    quarter_activity: QuarterActivity,
348) -> Unsigned36Bit {
349    match form {
350        SubwordForm::FullWord => {
351            // sign extension happens across all quarters.
352            match quarter_activity.first_active_quarter() {
353                Some(q) => {
354                    let extend_order: Vec<u8> = (q..(q + 4)).map(|q| q % 4).collect();
355                    sign_extend_quarters(word, quarter_activity, &extend_order)
356                }
357                None => word,
358            }
359        }
360        SubwordForm::Halves => {
361            // AABB: sign extension happens within AA and separately in BB
362            let left_activity = quarter_activity.masked_by(0b1100);
363            let sign_extended_on_lhs = match left_activity.first_active_quarter() {
364                None => word,
365                Some(first_active) => sign_extend_quarters(
366                    word,
367                    left_activity,
368                    match first_active {
369                        2 => &[2, 3],
370                        3 => &[3, 2],
371                        _ => unreachable!(),
372                    },
373                ),
374            };
375            let right_activity = quarter_activity.masked_by(0b0011);
376            let sign_extended_on_rhs = match right_activity.first_active_quarter() {
377                None => word,
378                Some(first_active) => sign_extend_quarters(
379                    word,
380                    right_activity,
381                    match first_active {
382                        0 => &[0, 1],
383                        1 => &[1, 0],
384                        _ => unreachable!(),
385                    },
386                ),
387            };
388            join_halves(
389                left_half(sign_extended_on_lhs),
390                right_half(sign_extended_on_rhs),
391            )
392        }
393        SubwordForm::ThreeOne => {
394            // AAAB: sign extension happens within AAA
395            let left_activity = quarter_activity.masked_by(0b1110);
396            match left_activity.first_active_quarter() {
397                None => word,
398                Some(first_active) => sign_extend_quarters(
399                    word,
400                    left_activity,
401                    match first_active {
402                        1 => &[1, 2, 3],
403                        2 => &[2, 3, 1],
404                        3 => &[3, 1, 2],
405                        _ => unreachable!(),
406                    },
407                ),
408            }
409        }
410        SubwordForm::Quarters => {
411            word // ABCD: Nothing to do.
412        }
413    }
414}
415
416#[test]
417fn test_sign_extend_full_word() {
418    use SubwordForm::*;
419
420    // When all quarters are active, sign extension should make no difference.
421    assert_octal_eq!(
422        sign_extend(
423            &FullWord,
424            Unsigned36Bit::from(0_u8),
425            QuarterActivity::new(0b1111)
426        ),
427        u36!(0)
428    );
429    assert_octal_eq!(
430        sign_extend(
431            &FullWord,
432            u36!(0o300_000_000_000),
433            QuarterActivity::new(0b1111)
434        ),
435        u36!(0o300_000_000_000)
436    );
437    assert_octal_eq!(
438        sign_extend(
439            &FullWord,
440            u36!(0o000_300_000_000),
441            QuarterActivity::new(0b1111)
442        ),
443        u36!(0o000_300_000_000)
444    );
445    assert_octal_eq!(
446        sign_extend(
447            &FullWord,
448            u36!(0o000_000_300_000),
449            QuarterActivity::new(0b1111)
450        ),
451        u36!(0o000_000_300_000)
452    );
453    assert_octal_eq!(
454        sign_extend(
455            &FullWord,
456            u36!(0o000_000_000_300),
457            QuarterActivity::new(0b1111)
458        ),
459        u36!(0o000_000_000_300)
460    );
461
462    // If no quarters are active, there is nothing to sign-extend from, so sign extension is a no-op.
463    assert_octal_eq!(
464        sign_extend(
465            &FullWord,
466            u36!(0o444333222111),
467            QuarterActivity::new(0b0000)
468        ),
469        u36!(0o444333222111)
470    );
471
472    // Sign-extending a positive quantity into a quarter should zero it.
473    assert_octal_eq!(
474        sign_extend(
475            &FullWord,
476            u36!(0o727_003_002_001),
477            QuarterActivity::new(0b0111)
478        ),
479        u36!(0o000_003_002_001)
480    );
481    assert_octal_eq!(
482        sign_extend(
483            &FullWord,
484            u36!(0o004_727_002_001),
485            QuarterActivity::new(0b1011)
486        ),
487        u36!(0o004_000_002_001)
488    );
489    assert_octal_eq!(
490        sign_extend(
491            &FullWord,
492            u36!(0o004_003_727_001),
493            QuarterActivity::new(0b1101)
494        ),
495        u36!(0o004_003_000_001)
496    );
497    assert_octal_eq!(
498        sign_extend(
499            &FullWord,
500            u36!(0o004_003_002_727),
501            QuarterActivity::new(0b1110)
502        ),
503        u36!(0o004_003_002_000)
504    );
505
506    // Sign-extending a negative quantity into a quarter should fill it with ones.
507    assert_octal_eq!(
508        sign_extend(
509            &FullWord,
510            u36!(0o272_403_002_001),
511            QuarterActivity::new(0b0111)
512        ),
513        u36!(0o777_403_002_001)
514    );
515    assert_octal_eq!(
516        sign_extend(
517            &FullWord,
518            u36!(0o004_272_402_001),
519            QuarterActivity::new(0b1011)
520        ),
521        u36!(0o004_777_402_001)
522    );
523    assert_octal_eq!(
524        sign_extend(
525            &FullWord,
526            u36!(0o004_003_272_401),
527            QuarterActivity::new(0b1101)
528        ),
529        u36!(0o004_003_777_401)
530    );
531    assert_octal_eq!(
532        sign_extend(
533            &FullWord,
534            u36!(0o404_003_002_272),
535            QuarterActivity::new(0b1110)
536        ),
537        u36!(0o404_003_002_777)
538    );
539
540    // We should be able to sign-extend over two consecutive quarters.
541    assert_octal_eq!(
542        sign_extend(
543            &FullWord,
544            u36!(0o727_003_002_727),
545            QuarterActivity::new(0b0110)
546        ),
547        u36!(0o000_003_002_000)
548    );
549    assert_octal_eq!(
550        sign_extend(
551            &FullWord,
552            u36!(0o727_727_002_001),
553            QuarterActivity::new(0b0011)
554        ),
555        u36!(0o000_000_002_001)
556    );
557    assert_octal_eq!(
558        sign_extend(
559            &FullWord,
560            u36!(0o004_727_727_001),
561            QuarterActivity::new(0b1001)
562        ),
563        u36!(0o004_000_000_001)
564    );
565    assert_octal_eq!(
566        sign_extend(
567            &FullWord,
568            u36!(0o004_003_727_727),
569            QuarterActivity::new(0b1100)
570        ),
571        u36!(0o004_003_000_000)
572    );
573
574    // We should be able to sign-extend a positive value over three consecutive quarters.
575    assert_octal_eq!(
576        sign_extend(
577            &FullWord,
578            u36!(0o727_727_727_001),
579            QuarterActivity::new(0b0001)
580        ),
581        u36!(0o000_000_000_001)
582    );
583    assert_octal_eq!(
584        sign_extend(
585            &FullWord,
586            u36!(0o727_727_002_727),
587            QuarterActivity::new(0b0010)
588        ),
589        u36!(0o000_000_002_000)
590    );
591    assert_octal_eq!(
592        sign_extend(
593            &FullWord,
594            u36!(0o727_003_727_727),
595            QuarterActivity::new(0b0100)
596        ),
597        u36!(0o000_003_000_000),
598    );
599    assert_octal_eq!(
600        sign_extend(
601            &FullWord,
602            u36!(0o004_727_727_727),
603            QuarterActivity::new(0b1000)
604        ),
605        u36!(0o004_000_000_000)
606    );
607
608    // We should be able to sign-extend a negative value over three consecutive quarters.
609    assert_octal_eq!(
610        sign_extend(
611            &FullWord,
612            u36!(0o727_727_727_401),
613            QuarterActivity::new(0b0001)
614        ),
615        u36!(0o777_777_777_401)
616    );
617    assert_octal_eq!(
618        sign_extend(
619            &FullWord,
620            u36!(0o727_727_402_727),
621            QuarterActivity::new(0b0010)
622        ),
623        u36!(0o777_777_402_777)
624    );
625    assert_octal_eq!(
626        sign_extend(
627            &FullWord,
628            u36!(0o727_403_727_727),
629            QuarterActivity::new(0b0100)
630        ),
631        u36!(0o777_403_777_777)
632    );
633    assert_octal_eq!(
634        sign_extend(
635            &FullWord,
636            u36!(0o404_727_727_727),
637            QuarterActivity::new(0b1000)
638        ),
639        u36!(0o404_777_777_777),
640    );
641}
642
643#[test]
644fn test_sign_extend_halves() {
645    use SubwordForm::*;
646
647    // When all quarters are active, sign extension should make no difference.
648    assert_octal_eq!(
649        sign_extend(&Halves, u36!(0), QuarterActivity::new(0b1111)),
650        u36!(0)
651    );
652    assert_octal_eq!(
653        sign_extend(
654            &Halves,
655            u36!(0o400_000_000_000),
656            QuarterActivity::new(0b1111)
657        ),
658        u36!(0o400_000_000_000)
659    );
660    assert_octal_eq!(
661        sign_extend(
662            &Halves,
663            u36!(0o000_400_000_000),
664            QuarterActivity::new(0b1111)
665        ),
666        u36!(0o000_400_000_000),
667    );
668    assert_octal_eq!(
669        sign_extend(
670            &Halves,
671            u36!(0o000_000_400_000),
672            QuarterActivity::new(0b1111)
673        ),
674        u36!(0o000_000_400_000)
675    );
676    assert_octal_eq!(
677        sign_extend(
678            &Halves,
679            u36!(0o000_000_000_400),
680            QuarterActivity::new(0b1111)
681        ),
682        u36!(0o000_000_000_400)
683    );
684
685    // If no quarters are active, there is nothing to sign-extend from, so sign extension is a no-op.
686    assert_octal_eq!(
687        sign_extend(
688            &Halves,
689            u36!(0o444_333_222_111),
690            QuarterActivity::new(0b0000)
691        ),
692        u36!(0o444_333_222_111)
693    );
694
695    // Sign-extending a positive quantity into a quarter should zero it.
696    assert_octal_eq!(
697        sign_extend(
698            &Halves,
699            u36!(0o004_003_002_001),
700            QuarterActivity::new(0b0101)
701        ),
702        u36!(0o000_003_000_001)
703    );
704    assert_octal_eq!(
705        sign_extend(
706            &Halves,
707            u36!(0o004_003_002_001),
708            QuarterActivity::new(0b0001)
709        ),
710        u36!(0o004_003_000_001)
711    );
712    assert_octal_eq!(
713        sign_extend(
714            &Halves,
715            u36!(0o004_003_002_001),
716            QuarterActivity::new(0b1010)
717        ),
718        u36!(0o004_000_002_000)
719    );
720    assert_octal_eq!(
721        sign_extend(
722            &Halves,
723            u36!(0o004_003_002_001),
724            QuarterActivity::new(0b0110)
725        ),
726        u36!(0o000_003_002_000)
727    );
728
729    // Sign-extending a negative quantity into a quarter should fill it with ones.
730    assert_octal_eq!(
731        sign_extend(
732            &Halves,
733            // Q3 is negative, so Q4 is set to 777
734            // Q1 is positive, so Q2 is set to 000
735            u36!(0o004_403_202_001),
736            QuarterActivity::new(0b0110)
737        ),
738        u36!(0o777_403_202_000)
739    );
740    assert_octal_eq!(
741        sign_extend(
742            &Halves,
743            u36!(0o404_003_402_001),
744            QuarterActivity::new(0b1010)
745        ),
746        u36!(0o404_777_402_777)
747    );
748    assert_octal_eq!(
749        sign_extend(
750            &Halves,
751            u36!(0o004_403_002_401),
752            QuarterActivity::new(0b0101)
753        ),
754        u36!(0o777_403_777_401)
755    );
756    assert_octal_eq!(
757        sign_extend(
758            &Halves,
759            u36!(0o004_003_002_401),
760            QuarterActivity::new(0b0001)
761        ),
762        u36!(0o004_003_777_401)
763    );
764
765    // We should not be able to sign-extend over more than two
766    // consecutive quarters (e.g. from Q1 to Q2 and then Q3), because
767    // the halves are only two quarters long.
768    assert_octal_eq!(
769        sign_extend(
770            &Halves,
771            u36!(0o004_003_002_001),
772            QuarterActivity::new(0b0001)
773        ),
774        u36!(0o004_003_000_001)
775    );
776    assert_octal_eq!(
777        sign_extend(
778            &Halves,
779            u36!(0o004_003_002_001),
780            QuarterActivity::new(0b0010)
781        ),
782        u36!(0o004_003_002_000)
783    );
784    assert_octal_eq!(
785        sign_extend(
786            &Halves,
787            u36!(0o004_003_002_001),
788            QuarterActivity::new(0b0100)
789        ),
790        u36!(0o000_003_002_001)
791    );
792    assert_octal_eq!(
793        sign_extend(
794            &Halves,
795            u36!(0o004_003_002_001),
796            QuarterActivity::new(0b1000)
797        ),
798        u36!(0o004_000_002_001)
799    );
800}
801
802// TODO: add tests for (27,9) and maybe (9,9,9,9).
803
804/// Determine which quarter of `source` gets permuted into
805/// `target_quarter` (numbered from zero), without regard to activity.
806/// Return the value of that quarter, shifted down into the lowest
807/// quarter (Q1, aka. quarter 0 when using u8 to indicate quarters).
808fn fetch_quarter(source_quarter: u8, source: &u64) -> u64 {
809    (source & quarter_mask(source_quarter)) >> (source_quarter * 9)
810}
811
812#[test]
813fn test_fetch_quarter() {
814    assert_octal_eq!(fetch_quarter(2, &0o444333222111), 0o333);
815}
816
817/// Copy bits from `source` to `dest`, permuting them according to
818/// `permutation`.  Only active quarters are modified.
819fn permute(
820    permutation: &Permutation,
821    direction: &ExchangeDirection,
822    active_quarters: &QuarterActivity,
823    source: &Unsigned36Bit,
824    dest: &Unsigned36Bit,
825) -> Unsigned36Bit {
826    let mut result: u64 = (*dest).into();
827    let source_bits: u64 = u64::from(*source);
828    for target_quarter in 0_u8..4_u8 {
829        let source_quarter: u8 = permutation_source(permutation, direction, target_quarter);
830
831        // The active quarters are specified according to the quarter
832        // number in the E register.  Hence for M->E (load-type)
833        // transfers, they are the quarters of the destination, but
834        // for E->M (store-type) transfers, they are the quarters of
835        // the source.  See the descriptions in the Users Handbook,
836        // sections 13-4.1 and 13-4.2.
837        let e_quarter: u8 = match *direction {
838            ExchangeDirection::ME => target_quarter,
839            ExchangeDirection::EM => source_quarter,
840        };
841        if active_quarters.is_active(&e_quarter) {
842            // `value` will be the value from the quarter we want,
843            // shifted to the correct position.
844            let value = fetch_quarter(source_quarter, &source_bits) << (target_quarter * 9);
845            let target_mask: u64 = quarter_mask(target_quarter);
846            result &= !target_mask;
847            result |= target_mask & value;
848        }
849    }
850    Unsigned36Bit::try_from(result).unwrap()
851}
852
853#[test]
854fn test_permute_p0() {
855    // P0 behaves the same in the ME and EM directions (q0<->q0,
856    // q1<->q1 etc.). Our choice of quarter activity here means that
857    // sign extension won't make a difference, so we get the same
858    // result for ME and EM.
859    for direction in &[ExchangeDirection::ME, ExchangeDirection::EM] {
860        assert_octal_eq!(
861            permute(
862                &Permutation::P0,
863                direction,
864                &QuarterActivity::new(0b1111),
865                &u36!(0o444333222111),
866                &u36!(0o777666555444),
867            ),
868            u36!(0o444333222111),
869        );
870        assert_octal_eq!(
871            permute(
872                &Permutation::P0,
873                direction,
874                &QuarterActivity::new(0b1110),
875                &u36!(0o444333222111),
876                &u36!(0o777666555444),
877            ),
878            u36!(0o444333222444),
879        );
880    }
881}
882
883/// Perform an exchange operation in the M->E direction, but without
884/// sign extension of the inactive quarters of the target.
885pub(crate) fn exchanged_value_for_load_without_sign_extension(
886    cfg: &SystemConfiguration,
887    source: &Unsigned36Bit,
888    dest: &Unsigned36Bit,
889) -> Unsigned36Bit {
890    permute(
891        &cfg.permutation(),
892        &ExchangeDirection::ME,
893        &cfg.active_quarters(),
894        source,
895        dest,
896    )
897}
898
899/// Perform an exchange operation suitable for a load operation; that
900/// is, emulate the operation of the exchange unit during e.g. LDA.
901pub(crate) fn exchanged_value_for_load(
902    cfg: &SystemConfiguration,
903    source: &Unsigned36Bit,
904    dest: &Unsigned36Bit,
905) -> Unsigned36Bit {
906    let permuted_target = exchanged_value_for_load_without_sign_extension(cfg, source, dest);
907    sign_extend(&cfg.subword_form(), permuted_target, cfg.active_quarters())
908}
909
910/// Perform an exchange operation suitable for a store operation; that
911/// is, emulate the operation of the exchange unit diring e.g. STE.
912///
913/// I believe that in this direction there is no sign extension.  This
914/// is based on my reading of Chapter 13 of the Technical Manual.
915/// Section 13-4.1 (covering loads) says "The sign extension process
916/// which follows step 4 is described in 13-4.3.". But section 13-4.2
917/// (covering stores) contains no similar statement. I suspect this
918/// means that sign extension does not happen for stores.
919pub(crate) fn exchanged_value_for_store(
920    cfg: &SystemConfiguration,
921    source: &Unsigned36Bit,
922    dest: &Unsigned36Bit,
923) -> Unsigned36Bit {
924    permute(
925        &cfg.permutation(),
926        &ExchangeDirection::EM,
927        &cfg.active_quarters(),
928        source,
929        dest,
930    )
931    // No sign extension in this direction, see doc comment for
932    // rationale.
933}
934
935pub(crate) fn standard_plugboard_f_memory_settings() -> [SystemConfiguration; 0o40] {
936    let mut result: [SystemConfiguration; 0o40] = {
937        let default_val = SystemConfiguration::from(0_u8);
938        [default_val; 32]
939    };
940    let plugboard = get_standard_plugboard();
941    for (i, spg_word) in plugboard.iter().take(0o10).enumerate() {
942        for quarter in 0..4 {
943            let index = (i * 4) + quarter;
944            let value: u64 = u64::from(*spg_word) >> (quarter * 9);
945            match SystemConfiguration::try_from((value & 0o777) as u16) {
946                Ok(v) => {
947                    event!(
948                        Level::TRACE,
949                        "F-memory index {:>03o} = {:>03o}",
950                        index,
951                        value
952                    );
953                    result[index] = v;
954                }
955                Err(_) => {
956                    panic!("conversion input value should be <= 0o777");
957                }
958            }
959        }
960    }
961    result
962}
963
964#[cfg(test)]
965mod tests {
966    use super::*;
967
968    #[test]
969    fn test_system_configuration_standard_config() {
970        /// The standard configuration set of F-memory, taken from table 7-2
971        /// (marked "OLD") of the TX-2 User's Guide (October 1961).
972        const STANDARD_CONFIG: [u16; 32] = [
973            0o000, 0o340, 0o342, 0o760, 0o761, 0o762, 0o763, 0o410, 0o411, 0o140, 0o142, 0o160,
974            0o161, 0o162, 0o163, 0o202, 0o200, 0o230, 0o232, 0o732, 0o733, 0o730, 0o731, 0o605,
975            0o600, 0o750, 0o670, 0o320, 0o333, 0o330, 0o331, 0o604,
976        ];
977
978        #[derive(Debug)]
979        struct Expectation {
980            config: SystemConfiguration,
981            perm: Permutation,
982            form: SubwordForm,
983            active: u8,
984        }
985        fn configval(n: u16) -> SystemConfiguration {
986            SystemConfiguration(Unsigned9Bit::try_from(n).expect("valid test data"))
987        }
988        use Permutation::*;
989        use SubwordForm::*;
990        let cases: [Expectation; 32] = [
991            // should be size 32 when we're ready
992            // Indexes 000 to 003
993            Expectation {
994                config: configval(0),
995                // (4,3,2,1) -> (4,3,2,1)
996                perm: P0,
997                form: FullWord,
998                active: 0b1111,
999            },
1000            Expectation {
1001                config: configval(0o340),
1002                // (2,1)->(2,1) i.e. R->R
1003                perm: P0,
1004                form: Halves,
1005                active: 0b0011,
1006            },
1007            Expectation {
1008                config: configval(0o342),
1009                // (4,3) -> (2, 1), i.e. L->R
1010                perm: P2,
1011                form: Halves,
1012                active: 0b0011,
1013            },
1014            Expectation {
1015                config: configval(0o760),
1016                // (1) -> (1)
1017                perm: P0,
1018                form: Quarters,
1019                active: 0b0001,
1020            },
1021            // Indexes 004 to 007
1022            Expectation {
1023                config: configval(0o761),
1024                // (2) -> (1)
1025                perm: P1,
1026                form: Quarters,
1027                active: 0b0001,
1028            },
1029            Expectation {
1030                config: configval(0o762),
1031                // (3) -> (1)
1032                perm: P2,
1033                form: Quarters,
1034                active: 0b0001,
1035            },
1036            Expectation {
1037                config: configval(0o763),
1038                // (4) -> (1)
1039                perm: P3,
1040                form: Quarters,
1041                active: 0b0001,
1042            },
1043            Expectation {
1044                config: configval(0o410),
1045                // (4,3,2) -> (4,3,2)
1046                perm: P0,
1047                form: ThreeOne,
1048                active: 0b1110,
1049            },
1050            // Indexes 010 to 013
1051            Expectation {
1052                config: configval(0o411),
1053                // (1,4,3) -> (4,3,2)
1054                perm: P1,
1055                form: ThreeOne,
1056                active: 0b1110,
1057            },
1058            Expectation {
1059                config: configval(0o140),
1060                // (2,1) -> (2,1), i.e. R->R and sign extend into L
1061                perm: P0,
1062                form: FullWord,
1063                active: 0b0011,
1064            },
1065            Expectation {
1066                config: configval(0o142),
1067                // (4,3) -> (2,1), i.e. L->R and sign extend into L
1068                perm: P2,
1069                form: FullWord,
1070                active: 0b0011,
1071            },
1072            Expectation {
1073                config: configval(0o160),
1074                // (1) -> (1) and sign extend into full word
1075                perm: P0,
1076                form: FullWord,
1077                active: 0b0001,
1078            },
1079            // Indexes 014 to 017
1080            Expectation {
1081                config: configval(0o161),
1082                // (2) -> (1) and sign extend into full word
1083                perm: P1,
1084                form: FullWord,
1085                active: 0b0001,
1086            },
1087            Expectation {
1088                config: configval(0o162),
1089                // (3) -> (1) and sign extend into full word
1090                perm: P2,
1091                form: FullWord,
1092                active: 0b0001,
1093            },
1094            Expectation {
1095                config: configval(0o163),
1096                // (4) -> (1) and sign extend into full word
1097                perm: P3,
1098                form: FullWord,
1099                active: 0b0001,
1100            },
1101            Expectation {
1102                config: configval(0o202),
1103                //  (4,3),(2,1) -> (2,1),(4,3), i.e. L,R -> R,L
1104                perm: P2,
1105                form: Halves,
1106                active: 0b1111,
1107            },
1108            // Indexes 020 to 023
1109            Expectation {
1110                config: configval(0o200),
1111                // (4,3),(2,1) -> (4,3),(2,1), i.e. L,R->L,R
1112                perm: P0,
1113                form: Halves,
1114                active: 0b1111,
1115            },
1116            Expectation {
1117                config: configval(0o230),
1118                // (4,3) -> (4,3), i.e L->L
1119                perm: P0,
1120                form: Halves,
1121                active: 0b1100,
1122            },
1123            Expectation {
1124                config: configval(0o232),
1125                // (2,1) -> (4,3), i.e. R->L
1126                perm: P2,
1127                form: Halves,
1128                active: 0b1100,
1129            },
1130            Expectation {
1131                config: configval(0o732),
1132                // (1) -> (3)
1133                perm: P2,
1134                form: Quarters,
1135                active: 0b0100,
1136            },
1137            // Indexes 024 to 027
1138            Expectation {
1139                config: configval(0o733),
1140                // (2) -> (3)
1141                perm: P3,
1142                form: Quarters,
1143                active: 0b0100,
1144            },
1145            Expectation {
1146                config: configval(0o730),
1147                // (3) -> (3)
1148                perm: P0,
1149                form: Quarters,
1150                active: 0b0100,
1151            },
1152            Expectation {
1153                config: configval(0o731),
1154                // (4) -> (3)
1155                perm: P1,
1156                form: Quarters,
1157                active: 0b0100,
1158            },
1159            Expectation {
1160                config: configval(0o605),
1161                // (4),(3),(2),(1) -> (1),(2),(3),(4)
1162                perm: P5,
1163                form: Quarters,
1164                active: 0b1111,
1165            },
1166            // Indexes 030 to 033
1167            Expectation {
1168                config: configval(0o600),
1169                // (4),(3),(2),(1) -> (4),(3),(2),(1)
1170                perm: P0,
1171                form: Quarters,
1172                active: 0b1111,
1173            },
1174            Expectation {
1175                config: configval(0o750),
1176                // (2) -> (2)
1177                perm: P0,
1178                form: Quarters,
1179                active: 0b0010,
1180            },
1181            Expectation {
1182                config: configval(0o670),
1183                // (4) -> (4)
1184                perm: P0,
1185                form: Quarters,
1186                active: 0b1000,
1187            },
1188            Expectation {
1189                config: configval(0o320),
1190                // (3),(1) -> (3),(1); sign extend (1) into L, (3) into R
1191                perm: P0,
1192                form: Halves,
1193                active: 0b0101,
1194            },
1195            // Indexes 034 to 037
1196            Expectation {
1197                config: configval(0o333),
1198                // (2) -> (3) and sign extend into L
1199                perm: P3,
1200                form: Halves,
1201                active: 0b0100,
1202            },
1203            Expectation {
1204                config: configval(0o330),
1205                // (3) -> (3) and sign extend into L
1206                perm: P0,
1207                form: Halves,
1208                active: 0b0100,
1209            },
1210            Expectation {
1211                config: configval(0o331),
1212                // (4) -> (3) and sign extend into L
1213                perm: P1,
1214                form: Halves,
1215                active: 0b0100,
1216            },
1217            Expectation {
1218                config: configval(0o604),
1219                // (4),(3),(2),(1) -> (3),(4),(1),(2)
1220                perm: P4,
1221                form: Quarters,
1222                active: 0b1111,
1223            },
1224        ];
1225
1226        // Validate the system configuration value in the current test
1227        // case against the values from table 7-2 in the user
1228        // handbook.
1229        for (index, case) in cases.iter().enumerate() {
1230            let cfg = Unsigned9Bit::try_from(STANDARD_CONFIG[index]).expect("valid test data");
1231            assert_eq!(
1232                cfg,
1233                Unsigned9Bit::from(case.config),
1234                "config in test case does not match standard config"
1235            );
1236        }
1237
1238        let f_memory = standard_plugboard_f_memory_settings();
1239        for (index, expectation) in cases.iter().enumerate() {
1240            let got = f_memory[index];
1241            assert_eq!(
1242                expectation.config, got,
1243                "config at index {} in test case does not match standard config: {:o} != {:o}",
1244                index, expectation.config, got,
1245            );
1246        }
1247
1248        for case in cases {
1249            assert_eq!(
1250                case.config.permutation(),
1251                case.perm,
1252                "non-matching permutation"
1253            );
1254            assert_eq!(
1255                case.config.subword_form(),
1256                case.form,
1257                "non-matching subword form"
1258            );
1259            for q in 0u8..4u8 {
1260                let expect_active = case.active & (1 << q) != 0;
1261                let got_active = case.config.active_quarters().is_active(&q);
1262                assert_eq!(
1263                    got_active,
1264                    expect_active,
1265                    "expected quarter activity {:?}, got quarter activity {:?}",
1266                    case.active,
1267                    case.config.active_quarters()
1268                );
1269            }
1270        }
1271    }
1272}