cpu/control/
op_jump.rs

1/// ## "Jump Skip Class" opcodes
2use base::prelude::*;
3use base::subword;
4
5use super::super::UpdateE;
6use super::super::alarm::{Alarm, AlarmDetails, Alarmer, BadMemOp, BugActivity};
7use super::super::context::Context;
8use super::super::control::{ControlUnit, OpcodeResult, ProgramCounterChange};
9use super::super::diagnostics::CurrentInstructionDiagnostics;
10use super::super::exchanger::exchanged_value_for_load_without_sign_extension;
11use super::super::memory::{BitChange, MemoryMapped, MemoryOpFailure, MemoryUnit, WordChange};
12
13/// ## "Jump Skip Class" opcodes
14impl ControlUnit {
15    /// Implements the JMP opcode and its variations (all of which are unconditional jumps).
16    pub(crate) fn op_jmp(
17        &mut self,
18        _ctx: &Context,
19        mem: &mut MemoryUnit,
20    ) -> Result<OpcodeResult, Alarm> {
21        // For JMP the configuration field in the instruction controls
22        // the behaviour of the instruction, without involving
23        // a load from F-memory.
24        fn nonzero(value: Unsigned5Bit) -> bool {
25            !value.is_zero()
26        }
27        let cf = self.regs.n.configuration();
28        let save_q = nonzero(cf & 0b01000_u8);
29        let savep_e = nonzero(cf & 0b00100_u8);
30        let savep_ix = nonzero(cf & 0b00010_u8);
31        let indexed = nonzero(cf & 0b00001_u8);
32        let j = self.regs.n.index_address();
33        let left: Unsigned18Bit = if save_q {
34            Unsigned18Bit::from(self.regs.q)
35        } else {
36            subword::left_half(mem.get_e_register())
37        };
38        let right: Unsigned18Bit = if savep_e {
39            Unsigned18Bit::from(self.regs.p)
40        } else {
41            subword::right_half(mem.get_e_register())
42        };
43        mem.set_e_register(subword::join_halves(left, right));
44
45        let (deferred, physical) = self.regs.n.operand_address().split();
46        if deferred {
47            // TODO: I don't know whether this is allowed or
48            // not, but if we disallow this for now, we can
49            // use any resulting error to identify cases where
50            // this is in fact used.
51            self.alarm_unit.fire_if_not_masked(
52                Alarm {
53                    sequence: self.regs.k,
54                    details: AlarmDetails::PSAL(
55                        u32::from(self.regs.n.operand_address_and_defer_bit()),
56                        format!(
57                            "JMP target has deferred address {:#o}",
58                            self.regs.n.operand_address()
59                        ),
60                    ),
61                },
62                &self.regs.diagnostic_only,
63            )?;
64            // If deferred addressing is allowed for JMP, we will
65            // need to implement it.  It's not yet implemented.
66            return Err(self.alarm_unit.always_fire(
67                Alarm {
68                    sequence: self.regs.k,
69                    details: AlarmDetails::ROUNDTUITAL {
70                        explanation: "deferred JMP is not yet implemented".to_string(),
71                        bug_report_url: "https://github.com/TX-2/TX-2-simulator/issues/141",
72                    },
73                },
74                &self.regs.diagnostic_only,
75            ));
76        }
77
78        let new_pc: Address = if indexed {
79            physical.index_by(self.regs.get_index_register(j))
80        } else {
81            physical
82        };
83
84        // Now that we have used Xj, we can overwrite the original
85        // value if we need to save P in it.
86        if savep_ix && j != Unsigned6Bit::ZERO {
87            let p = self.regs.p;
88            self.regs.set_index_register_from_address(j, &p);
89        }
90
91        if nonzero(cf & 0b10000_u8) {
92            self.dismiss_unless_held("JMP has dismiss bit set in config syllable");
93        }
94        Ok(OpcodeResult {
95            program_counter_change: Some(ProgramCounterChange::Jump(new_pc)),
96            poll_order_change: None,
97            output: None,
98        })
99    }
100
101    /// The SKM instruction.  This has a number of supernumerary
102    /// mnemonics.  The index address field of the instruction
103    /// identifies which bit (within the target word) to operate on,
104    /// and the instruction configuration value determines both how to
105    /// manipulate that bit, and what to do on the basis of its
106    /// original value.
107    ///
108    /// The SKM instruction is documented on pages 7-34 and 7-35 of
109    /// the TX-2 User Handbook.
110    pub(crate) fn op_skm(
111        &mut self,
112        ctx: &Context,
113        mem: &mut MemoryUnit,
114    ) -> Result<OpcodeResult, Alarm> {
115        let bit = index_address_to_bit_selection(self.regs.n.index_address());
116        // Determine the operand address; any initial deferred cycle
117        // must use 0 as the indexation, as the index address of the
118        // SKM instruction is used to identify the bit to operate on.
119        let target = self.resolve_operand_address(ctx, mem, Some(Unsigned6Bit::ZERO))?;
120        let cf: u8 = u8::from(self.regs.n.configuration());
121        let change: WordChange = WordChange {
122            bit,
123            bitop: match cf & 0b11 {
124                0b00 => None,
125                0b01 => Some(BitChange::Flip),
126                0b10 => Some(BitChange::Clear),
127                0b11 => Some(BitChange::Set),
128                _ => unreachable!(),
129            },
130            cycle: cf & 0b100 != 0,
131        };
132        let prev_bit_value: Option<bool> = match mem.change_bit(ctx, &target, &change) {
133            Ok(prev) => prev,
134            Err(MemoryOpFailure::NotMapped(addr)) => {
135                self.alarm_unit.fire_if_not_masked(Alarm {
136                    sequence: self.regs.k,
137                    details: AlarmDetails::QSAL(
138                        self.regs.n,
139                        BadMemOp::Write(target.into()),
140                        format!(
141                            "SKM instruction attempted to access address {addr:o} but it is not mapped",
142                        ),
143                    ),
144                }, &self.regs.diagnostic_only)?;
145                // The alarm is masked.  We turn the memory mutation into a no-op.
146                return Ok(OpcodeResult::default());
147            }
148            Err(MemoryOpFailure::ReadOnly(_, _)) => {
149                self.alarm_unit.fire_if_not_masked(
150                    Alarm {
151                        sequence: self.regs.k,
152                        details: AlarmDetails::QSAL(
153                            self.regs.n,
154                            BadMemOp::Write(target.into()),
155                            format!(
156                                "SKM instruction attempted to modify (instruction configuration={cf:o}) a read-only location {target:o}",
157                            ),
158                        )
159                    }, &self.regs.diagnostic_only)?;
160                // The alarm is masked.  We turn the memory mutation into a no-op.
161                return Ok(OpcodeResult::default());
162            }
163        };
164        let skip: bool = if let Some(prevbit) = prev_bit_value {
165            match (cf >> 3) & 3 {
166                0b00 => false,
167                0b01 => true,
168                0b10 => !prevbit,
169                0b11 => prevbit,
170                _ => unreachable!(),
171            }
172        } else {
173            // The index address specified a nonexistent bit
174            // (e.g. 1.0) and so we do not perform a skip.
175            false
176        };
177        Ok(OpcodeResult {
178            // The location of the currently executing instruction is
179            // referred to by M4 as '#'.  The next instruction would
180            // be '#+1' and that's where the P register currently
181            // points.  But "skip" means to set P=#+2.
182            program_counter_change: if skip {
183                Some(ProgramCounterChange::CounterUpdate)
184            } else {
185                None
186            },
187            poll_order_change: None,
188            output: None,
189        })
190    }
191
192    /// Implement the SED instruction. This is described on page 3-36
193    /// of the Users Handbook.
194    pub(crate) fn op_sed(
195        &mut self,
196        ctx: &Context,
197        mem: &mut MemoryUnit,
198    ) -> Result<OpcodeResult, Alarm> {
199        let existing_e_value = mem.get_e_register();
200        let target: Address = self.operand_address_with_optional_defer_and_index(ctx, mem)?;
201        // `fetch_operand_from_address_with_exchange` would perform
202        // sign extension even though this is supposed to have no
203        // effect in the SED instruction.
204        let (mut word, _extra) =
205            self.fetch_operand_from_address_without_exchange(ctx, mem, &target, &UpdateE::No)?;
206        if mem.get_e_register() != existing_e_value {
207            let diags: CurrentInstructionDiagnostics = self.regs.diagnostic_only.clone();
208            return Err(self.always_fire(
209                Alarm {
210                    sequence: self.regs.k,
211                    details: AlarmDetails::BUGAL {
212                        activity: BugActivity::Opcode,
213                        diagnostics: diags.clone(),
214                        message: "memory fetch during execution of SED changed the E register"
215                            .to_string(),
216                    },
217                },
218                &diags,
219            ));
220        }
221
222        // Perform active-quarter masking and permutation, but not sign
223        // extension.  Inactive quarters of the result are taken from
224        // the existing value of self.regs.e.
225        word = exchanged_value_for_load_without_sign_extension(
226            &self.get_config(),
227            &word,
228            &mem.get_e_register(),
229        );
230
231        Ok(OpcodeResult {
232            program_counter_change: if word == mem.get_e_register() {
233                None
234            } else {
235                // The location of the currently executing instruction
236                // is referred to by M4 as '#'.  The next instruction
237                // would be '#+1' and that's where the P register
238                // currently points.  But "skip" means to set P=#+2.
239                Some(ProgramCounterChange::CounterUpdate)
240            },
241            poll_order_change: None,
242            output: None,
243        })
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use super::super::super::alarm::Alarm;
250    use super::super::super::context::Context;
251    use super::super::super::control::{
252        ConfigurationMemorySetup, OpcodeResult, PanicOnUnmaskedAlarm, ProgramCounterChange, UpdateE,
253    };
254    use super::super::super::memory::MetaBitChange;
255    use super::super::super::{ControlUnit, MemoryConfiguration, MemoryUnit};
256    use base::instruction::{Opcode, SymbolicInstruction};
257    use base::prelude::*;
258    use core::time::Duration;
259
260    fn make_ctx() -> Context {
261        Context {
262            simulated_time: Duration::new(42, 42),
263            real_elapsed_time: Duration::new(7, 12),
264        }
265    }
266
267    fn setup(
268        ctx: &Context,
269        j: Unsigned6Bit,
270        initial: Signed18Bit,
271        e: Unsigned36Bit,
272        p: Address,
273        q: Address,
274    ) -> (ControlUnit, MemoryUnit) {
275        let mut control = ControlUnit::new(
276            PanicOnUnmaskedAlarm::Yes,
277            ConfigurationMemorySetup::StandardForTestingOnly,
278        );
279        let mut mem = MemoryUnit::new(
280            ctx,
281            &MemoryConfiguration {
282                with_u_memory: false,
283            },
284        );
285        if j == Unsigned6Bit::ZERO {
286            assert_eq!(initial, 0, "Cannot set X₀ to a nonzero value");
287        } else {
288            control.regs.set_index_register(j, &initial);
289        }
290        mem.set_e_register(e);
291        control.regs.p = p;
292        control.regs.q = q;
293        control.regs.k = Some(Unsigned6Bit::ZERO);
294        control.regs.flags.lower_all();
295        control.regs.flags.raise(&SequenceNumber::ZERO);
296        (control, mem)
297    }
298
299    /// Simulate a JMP instruction; return (destination, Xj, E, dismissed).
300    fn simulate_jmp(
301        ctx: &Context,
302        j: Unsigned6Bit,
303        initial: Signed18Bit,
304        e: Unsigned36Bit,
305        p: Address,
306        q: Address,
307        inst: &SymbolicInstruction,
308    ) -> (Address, Signed18Bit, Unsigned36Bit, bool) {
309        const COMPLAIN: &str = "failed to set up JMP test data";
310        let (mut control, mut mem) = setup(ctx, j, initial, e, p, q);
311        control
312            .update_n_register(Instruction::from(inst).bits())
313            .expect(COMPLAIN);
314        let result = control.op_jmp(ctx, &mut mem);
315        match result {
316            Err(e) => {
317                panic!("JMP instruction failed: {e}");
318            }
319            Ok(OpcodeResult {
320                program_counter_change: Some(ProgramCounterChange::Jump(to)),
321                poll_order_change: None,
322                output: None,
323            }) => {
324                let xj = control.regs.get_index_register(j);
325                let dismissed = !control.regs.flags.current_flag_state(&SequenceNumber::ZERO);
326                (to, xj, mem.get_e_register(), dismissed)
327            }
328            other => {
329                panic!("JMP didn't jump in the expected way: {other:?}");
330            }
331        }
332    }
333
334    /// This test is based on example 1 for JMP on page 3-30 of the
335    /// Users Handbook.
336    #[test]
337    fn test_jmp_example_1_jmp() {
338        let context = make_ctx();
339        let expected_target = Address::from(u18!(0o3733));
340        let orig_xj = Signed18Bit::from(20_i8);
341        let orig_e = u36!(0o606_202_333_123);
342        let (target, xj, e, dismissed) = simulate_jmp(
343            &context,
344            u6!(1),
345            orig_xj,
346            orig_e,
347            Address::from(u18!(0o1000)),
348            Address::from(u18!(0o2777)),
349            &SymbolicInstruction {
350                held: false,
351                configuration: Unsigned5Bit::ZERO, // plain JMP
352                opcode: Opcode::Jmp,
353                index: u6!(1),
354                operand_address: OperandAddress::direct(expected_target),
355            },
356        );
357        // ⁰JMP₁ ignores the index bits (as the least significant bit
358        // of the configuration syllable is unset).
359        assert_eq!(target, expected_target);
360        assert_eq!(xj, orig_xj); // unaffected
361        assert_eq!(e, orig_e); // unaffected
362        assert!(!dismissed);
363    }
364
365    /// This test is based on example 2 for JMP on page 3-30 of the
366    /// Users Handbook.
367    #[test]
368    fn test_jmp_example_2_brc() {
369        let context = make_ctx();
370        let target_base = Address::from(u18!(0o3702));
371        let orig_xj = Signed18Bit::from(0o20_i8);
372        let orig_e = u36!(0o606_202_333_123);
373        let (target, xj, e, dismissed) = simulate_jmp(
374            &context,
375            u6!(1),
376            orig_xj,
377            orig_e,
378            Address::from(u18!(0o1000)), // p
379            Address::from(u18!(0o2777)), // q
380            &SymbolicInstruction {
381                held: false,
382                configuration: u5!(1), // BRC
383                opcode: Opcode::Jmp,
384                index: u6!(1),
385                operand_address: OperandAddress::direct(target_base),
386            },
387        );
388        // ¹JMP₁ is an indexed jump.
389        assert_eq!(target, Address::from(u18!(0o3722)));
390        assert_eq!(xj, orig_xj); // unaffected
391        assert_eq!(e, orig_e); // unaffected
392        assert!(!dismissed);
393    }
394
395    /// This test is based on example 3 for JMP on page 3-30 of the
396    /// Users Handbook.
397    #[test]
398    fn test_jmp_example_3_jps() {
399        let context = make_ctx();
400        let target_base = Address::from(u18!(0o3702));
401        let orig_xj = Signed18Bit::from(0o20_i8);
402        let orig_e = u36!(0o606_202_333_123);
403        let (target, xj, e, dismissed) = simulate_jmp(
404            &context,
405            u6!(1),
406            orig_xj,
407            orig_e,
408            Address::from(u18!(0o1000)), // p
409            Address::from(u18!(0o2777)), // q
410            &SymbolicInstruction {
411                held: false,
412                configuration: u5!(2), // JPS ("jump and save")
413                opcode: Opcode::Jmp,
414                index: u6!(1),
415                operand_address: OperandAddress::direct(target_base),
416            },
417        );
418        // ²JMP₁ saves #+1 in X₁.
419        assert_eq!(target, target_base);
420        assert_eq!(xj, u18!(0o1000).reinterpret_as_signed()); // changed
421        assert_eq!(e, orig_e); // unaffected
422        assert!(!dismissed);
423    }
424
425    /// This test is based on example 4 for JMP on page 3-30 of the
426    /// Users Handbook.
427    #[test]
428    fn test_jmp_example_4_brs() {
429        let context = make_ctx();
430        let target_base = Address::from(u18!(0o3302));
431        let orig_xj = Signed18Bit::from(0o20_i8);
432        let orig_e = u36!(0o606_202_333_123);
433        let (target, xj, e, dismissed) = simulate_jmp(
434            &context,
435            u6!(1),                      // j
436            orig_xj,                     // Xj
437            orig_e,                      // E
438            Address::from(u18!(0o200)),  // P
439            Address::from(u18!(0o2777)), // Q
440            &SymbolicInstruction {
441                held: false,
442                configuration: u5!(3), // BRS ("branch and save")
443                opcode: Opcode::Jmp,
444                index: u6!(1),
445                operand_address: OperandAddress::direct(target_base),
446            },
447        );
448        // ³JMP₁ saves #+1 in X₁ and is an indexed (by X₁) jump.
449        let expected_target = target_base.index_by(orig_xj);
450        assert_eq!(target, expected_target);
451        assert_eq!(xj, u18!(0o200).reinterpret_as_signed()); // changed (saved P)
452        assert_eq!(e, orig_e); // unaffected
453        assert!(!dismissed);
454    }
455
456    /// This test is based on example 5 for JMP on page 3-30 of the
457    /// Users Handbook.
458    #[test]
459    fn test_jmp_example_5() {
460        let context = make_ctx();
461        let target_base = Address::from(u18!(0o3302));
462        let orig_xj = Signed18Bit::from(0o20_i8);
463        let orig_e = u36!(0o606_202_333_123);
464        let (target, xj, e, dismissed) = simulate_jmp(
465            &context,
466            u6!(1),                      // j
467            orig_xj,                     // Xj
468            orig_e,                      // E
469            Address::from(u18!(0o200)),  // P
470            Address::from(u18!(0o2777)), // Q
471            &SymbolicInstruction {
472                held: false,
473                configuration: u5!(4),
474                opcode: Opcode::Jmp,
475                index: u6!(1),
476                operand_address: OperandAddress::direct(target_base),
477            },
478        );
479        // ⁴JMP₁ saves P in R(E).
480        assert_eq!(target, target_base);
481        assert_eq!(xj, orig_xj); // unaffected
482        assert_eq!(e, join_halves(left_half(orig_e), u18!(0o200))); // saved #+1
483        assert!(!dismissed);
484    }
485
486    /// This test is based on example 6 for JMP on page 3-30 of the
487    /// Users Handbook.
488    #[test]
489    fn test_jmp_example_6_brc() {
490        let context = make_ctx();
491        let target_base = Address::from(u18!(0o3302));
492        let orig_xj = Signed18Bit::from(0o20_i8);
493        let orig_e = u36!(0o606_202_333_123);
494        let (target, xj, e, dismissed) = simulate_jmp(
495            &context,
496            u6!(1),                      // j
497            orig_xj,                     // Xj
498            orig_e,                      // E
499            Address::from(u18!(0o200)),  // P
500            Address::from(u18!(0o2777)), // Q
501            &SymbolicInstruction {
502                held: false,
503                configuration: u5!(5), // BRC
504                opcode: Opcode::Jmp,
505                index: u6!(1),
506                operand_address: OperandAddress::direct(target_base),
507            },
508        );
509        // ⁵JMP₁ saves P in R(E) and is an indexed jump
510        assert_eq!(target, Address::from(u18!(0o3322)));
511        assert_eq!(xj, orig_xj); // unaffected
512        assert_eq!(e, join_halves(left_half(orig_e), u18!(0o200))); // saved #+1
513        assert!(!dismissed);
514    }
515
516    /// This test is based on example 7 for JMP on page 3-30 of the
517    /// Users Handbook.
518    #[test]
519    fn test_jmp_example_7_jps() {
520        let context = make_ctx();
521        let target_base = Address::from(u18!(0o3302));
522        let orig_xj = Signed18Bit::from(0o20_i8);
523        let orig_e = u36!(0o606_202_333_123);
524        let (target, xj, e, dismissed) = simulate_jmp(
525            &context,
526            u6!(1),                      // j
527            orig_xj,                     // Xj
528            orig_e,                      // E
529            Address::from(u18!(0o200)),  // P
530            Address::from(u18!(0o2777)), // Q
531            &SymbolicInstruction {
532                held: false,
533                configuration: u5!(6), // JPS ("jump and save")
534                opcode: Opcode::Jmp,
535                index: u6!(1),
536                operand_address: OperandAddress::direct(target_base),
537            },
538        );
539        // ⁶JMP₁ saves P (=#+1) in both R(E) and in X₁.
540        assert_eq!(target, Address::from(u18!(0o3302)));
541        assert_eq!(xj, u18!(0o200).reinterpret_as_signed()); // saved P
542        assert_eq!(e, join_halves(left_half(orig_e), u18!(0o200))); // saved P
543        assert!(!dismissed);
544    }
545
546    /// This test is based on example 8 for JMP on page 3-31 of the
547    /// Users Handbook.
548    #[test]
549    fn test_jmp_example_8_brs() {
550        let context = make_ctx();
551        let target_base = Address::from(u18!(0o3302));
552        let orig_xj = Signed18Bit::from(0o20_i8);
553        let orig_e = u36!(0o606_202_333_123);
554        let (target, xj, e, dismissed) = simulate_jmp(
555            &context,
556            u6!(1),                      // j
557            orig_xj,                     // Xj
558            orig_e,                      // E
559            Address::from(u18!(0o200)),  // P
560            Address::from(u18!(0o2777)), // Q
561            &SymbolicInstruction {
562                held: false,
563                configuration: u5!(7), // BRS ("branch and save")
564                opcode: Opcode::Jmp,
565                index: u6!(1),
566                operand_address: OperandAddress::direct(target_base),
567            },
568        );
569        // ⁷JMP₁ is an indexed jump and saves P (=#+1) in both R(E)
570        // and in X₁.
571        assert_eq!(target, Address::from(u18!(0o3322)));
572        assert_eq!(xj, u18!(0o200).reinterpret_as_signed()); // saved P
573        assert_eq!(e, join_halves(left_half(orig_e), u18!(0o200))); // saved P
574        assert!(!dismissed);
575    }
576
577    /// This test is based on example 9 for JMP on page 3-31 of the
578    /// Users Handbook.
579    #[test]
580    fn test_jmp_example_9() {
581        let context = make_ctx();
582        let target_base = Address::from(u18!(0o3302));
583        let orig_xj = Signed18Bit::from(0o20_i8);
584        let orig_e = u36!(0o606_202_333_123);
585        let orig_q = u18!(0o2777);
586
587        let (target, xj, e, dismissed) = simulate_jmp(
588            &context,
589            u6!(1),                     // j
590            orig_xj,                    // Xj
591            orig_e,                     // E
592            Address::from(u18!(0o200)), // P
593            Address::from(orig_q),      // Q
594            &SymbolicInstruction {
595                held: false,
596                configuration: u5!(0o10),
597                opcode: Opcode::Jmp,
598                index: u6!(1),
599                operand_address: OperandAddress::direct(target_base),
600            },
601        );
602        // ¹⁰JMP₁ saves the location of the last memory reference
603        // (that is, the value of the Q register) in L(E).
604        assert_eq!(target, Address::from(u18!(0o3302)));
605        assert_eq!(xj, orig_xj); // unaffected
606        assert_eq!(e, join_halves(orig_q, right_half(orig_e))); // saved Q
607        assert!(!dismissed);
608    }
609
610    /// This test is based on example 10 for JMP on page 3-31 of the
611    /// Users Handbook.
612    #[test]
613    fn test_jmp_example_10_jpq() {
614        let context = make_ctx();
615        let target_base = Address::from(u18!(0o3302));
616        let orig_xj = Signed18Bit::from(0o20_i8);
617        let orig_e = u36!(0o606_202_333_123);
618        let orig_q = u18!(0o2777);
619        let orig_p = u18!(0o200);
620        let (target, xj, e, dismissed) = simulate_jmp(
621            &context,
622            u6!(1),                // j
623            orig_xj,               // Xj
624            orig_e,                // E
625            Address::from(orig_p), // P
626            Address::from(orig_q), // Q
627            &SymbolicInstruction {
628                held: false,
629                configuration: u5!(0o14), // JPQ
630                opcode: Opcode::Jmp,
631                index: u6!(1),
632                operand_address: OperandAddress::direct(target_base),
633            },
634        );
635        // ¹⁴JMP₁ saves the location of the last memory reference
636        // (that is, the value of the Q register) in L(E), the value
637        // of P in R(E).
638        assert_eq!(target, Address::from(u18!(0o3302)));
639        assert_eq!(xj, orig_xj); // unaffected
640        assert_eq!(e, join_halves(orig_q, orig_p)); // saved Q, P
641        assert!(!dismissed);
642    }
643
644    /// This test is based on example 11 for JMP on page 3-31 of the
645    /// Users Handbook.
646    #[test]
647    fn test_jmp_example_11_bpq() {
648        let context = make_ctx();
649        let target_base = Address::from(u18!(0o3302));
650        let orig_xj = Signed18Bit::from(0o20_i8);
651        let orig_e = u36!(0o606_202_333_123);
652        let orig_q = u18!(0o2777);
653        let orig_p = u18!(0o200);
654        let (target, xj, e, dismissed) = simulate_jmp(
655            &context,
656            u6!(1),                // j
657            orig_xj,               // Xj
658            orig_e,                // E
659            Address::from(orig_p), // P
660            Address::from(orig_q), // Q
661            &SymbolicInstruction {
662                held: false,
663                configuration: u5!(0o15), // BPQ
664                opcode: Opcode::Jmp,
665                index: u6!(1),
666                operand_address: OperandAddress::direct(target_base),
667            },
668        );
669        // ¹⁵JMP₁ saves the location of the last memory reference
670        // (that is, the value of the Q register) in L(E), the value
671        // of P in R(E), and it is an indexed jump.
672        assert_eq!(target, Address::from(u18!(0o3322)));
673        assert_eq!(xj, orig_xj); // unaffected
674        assert_eq!(e, join_halves(orig_q, orig_p)); // saved Q, P
675        assert!(!dismissed);
676    }
677
678    /// This test is based on example 12 for JMP on page 3-31 of the
679    /// Users Handbook.
680    #[test]
681    fn test_jmp_example_12_jes() {
682        let context = make_ctx();
683        let target_base = Address::from(u18!(0o3302));
684        let orig_xj = Signed18Bit::from(0o20_i8);
685        let orig_e = u36!(0o606_202_333_123);
686        let orig_q = u18!(0o2777);
687        let orig_p = u18!(0o200);
688        let (target, xj, e, dismissed) = simulate_jmp(
689            &context,
690            u6!(1),                // j
691            orig_xj,               // Xj
692            orig_e,                // E
693            Address::from(orig_p), // P
694            Address::from(orig_q), // Q
695            &SymbolicInstruction {
696                held: false,
697                configuration: u5!(0o16),
698                opcode: Opcode::Jmp,
699                index: u6!(1),
700                operand_address: OperandAddress::direct(target_base),
701            },
702        );
703        // ¹⁶JMP₁ saves P in R(E) and Xj, and saves Q in L(E).
704        assert_eq!(target, Address::from(u18!(0o3302)));
705        assert_eq!(xj, orig_p.reinterpret_as_signed()); // saved P
706        assert_eq!(e, join_halves(orig_q, orig_p)); // saved P, Q
707        assert!(!dismissed);
708    }
709
710    /// This test is based on example 13 for JMP on page 3-31 of the
711    /// Users Handbook.
712    #[test]
713    fn test_jmp_example_13() {
714        let context = make_ctx();
715        let target_base = Address::from(u18!(0o3302));
716        let orig_xj = Signed18Bit::from(0o20_i8);
717        let orig_e = u36!(0o606_202_333_123);
718        let orig_q = u18!(0o2777);
719        let orig_p = u18!(0o200);
720        let (target, xj, e, dismissed) = simulate_jmp(
721            &context,
722            u6!(1),                // j
723            orig_xj,               // Xj
724            orig_e,                // E
725            Address::from(orig_p), // P
726            Address::from(orig_q), // Q
727            &SymbolicInstruction {
728                held: false,
729                configuration: u5!(0o20),
730                opcode: Opcode::Jmp,
731                index: u6!(1),
732                operand_address: OperandAddress::direct(target_base),
733            },
734        );
735        // ²⁰JMP₁ is jump, dismiss (no saves, not indexed).
736        assert_eq!(target, Address::from(u18!(0o3302)));
737        assert_eq!(xj, orig_xj); // unaffected
738        assert_eq!(e, orig_e); // unaffected
739        assert!(dismissed); // dismiss current sequence
740    }
741
742    /// This test is based on example 14 for JMP on page 3-31 of the
743    /// Users Handbook.
744    #[test]
745    fn test_jmp_example_14() {
746        let context = make_ctx();
747        let target_base = Address::from(u18!(0o3302));
748        let orig_xj = Signed18Bit::from(0o20_i8);
749        let orig_e = u36!(0o606_202_333_123);
750        let orig_q = u18!(0o2777);
751        let orig_p = u18!(0o200);
752        let (target, xj, e, dismissed) = simulate_jmp(
753            &context,
754            u6!(1),                // j
755            orig_xj,               // Xj
756            orig_e,                // E
757            Address::from(orig_p), // P
758            Address::from(orig_q), // Q
759            &SymbolicInstruction {
760                held: false,
761                configuration: u5!(0o21),
762                opcode: Opcode::Jmp,
763                index: u6!(1),
764                operand_address: OperandAddress::direct(target_base),
765            },
766        );
767        // ²¹JMP₁ is indexed jump, dismiss (no saves).
768        assert_eq!(target, Address::from(u18!(0o3322)));
769        assert_eq!(xj, orig_xj); // unaffected
770        assert_eq!(e, orig_e); // unaffected
771        assert!(dismissed); // dismiss current sequence
772    }
773
774    /// This test is based on example 15 for JMP on page 3-31 of the
775    /// Users Handbook.
776    #[test]
777    fn test_jmp_example_15() {
778        let context = make_ctx();
779        let target_base = Address::from(u18!(0o3302));
780        let orig_xj = Signed18Bit::from(0o20_i8);
781        let orig_e = u36!(0o606_202_333_123);
782        let orig_q = u18!(0o2777);
783        let orig_p = u18!(0o200);
784        let (target, xj, e, dismissed) = simulate_jmp(
785            &context,
786            u6!(1),                // j
787            orig_xj,               // Xj
788            orig_e,                // E
789            Address::from(orig_p), // P
790            Address::from(orig_q), // Q
791            &SymbolicInstruction {
792                held: false,
793                configuration: u5!(0o22),
794                opcode: Opcode::Jmp,
795                index: u6!(1),
796                operand_address: OperandAddress::direct(target_base),
797            },
798        );
799        // ²²JMP₁ is jump, save P in Xⱼ, dismiss (not indexed).
800        assert_eq!(target, Address::from(u18!(0o3302)));
801        assert_eq!(xj, orig_p.reinterpret_as_signed()); // P is saved in Xⱼ.
802        assert_eq!(e, orig_e); // unaffected
803        assert!(dismissed); // dismiss current sequence
804    }
805
806    /// This test is based on example 16 for JMP on page 3-31 of the
807    /// Users Handbook.
808    #[test]
809    fn test_jmp_example_16() {
810        let context = make_ctx();
811        let target_base = Address::from(u18!(0o3302));
812        let orig_xj = Signed18Bit::from(0o20_i8);
813        let orig_e = u36!(0o606_202_333_123);
814        let orig_q = u18!(0o2777);
815        let orig_p = u18!(0o200);
816        let (target, xj, e, dismissed) = simulate_jmp(
817            &context,
818            u6!(1),                // j
819            orig_xj,               // Xj
820            orig_e,                // E
821            Address::from(orig_p), // P
822            Address::from(orig_q), // Q
823            &SymbolicInstruction {
824                held: false,
825                configuration: u5!(0o23),
826                opcode: Opcode::Jmp,
827                index: u6!(1),
828                operand_address: OperandAddress::direct(target_base),
829            },
830        );
831        // ²³JMP₁ is indexed jump, save P in Xⱼ, dismiss.
832        assert_eq!(target, Address::from(u18!(0o3322)));
833        assert_eq!(xj, orig_p.reinterpret_as_signed()); // P is saved in Xⱼ.
834        assert_eq!(e, orig_e); // unaffected
835        assert!(dismissed); // dismiss current sequence
836    }
837
838    /// Simulate a SED instruction.
839    fn simulate_sed(
840        ctx: &Context,
841        e: Unsigned36Bit, // initial content of E register
842        j: Unsigned6Bit,
843        xj: Signed18Bit,           // initial value of Xj
844        initial_tj: Unsigned36Bit, // initial content of Tj
845        p: Address,
846        inst: &SymbolicInstruction,
847    ) -> Result<(Option<Alarm>, bool), String> {
848        const COMPLAIN: &str = "failed to set up SED test data";
849        let data_address: Address = match inst.operand_address.split() {
850            (true, _) => {
851                panic!("simulate_sed doesn't support deferred addressing yet")
852            }
853            (false, a) => a.index_by(xj),
854        };
855
856        let (mut control, mut mem) = setup(ctx, j, xj, e, p, Address::ZERO);
857        if let Err(e) = control.memory_store_without_exchange(
858            ctx,
859            &mut mem,
860            &data_address,
861            &initial_tj,
862            &UpdateE::No,
863            &MetaBitChange::None,
864        ) {
865            return Err(format!("failed to set up memory contents: {e}"));
866        }
867        control
868            .update_n_register(Instruction::from(inst).bits())
869            .expect(COMPLAIN);
870        let result = match control.op_sed(ctx, &mut mem) {
871            Err(e) => {
872                return Err(format!("Execution of SED instruction failed: {e}"));
873            }
874            Ok(result) => result,
875        };
876        if result.output.is_some() {
877            return Err("SED instruction should not generate output".to_string());
878        }
879        if result.poll_order_change.is_some() {
880            return Err("SED instruction should not change sequence flags".to_string());
881        }
882
883        // Check that the E register was not changed.
884        if mem.get_e_register() != e {
885            return Err(format!(
886                "SED instruction incorrectly changed register E from {:o} to {:o}",
887                e,
888                mem.get_e_register()
889            ));
890        }
891        match result.program_counter_change {
892            Some(ProgramCounterChange::CounterUpdate) => Ok((None, true)), // skip
893            None => Ok((None, false)),                                     // no skip
894            Some(ProgramCounterChange::Jump(_)) => Err(format!(
895                "SED instruction performed an unexpected jump {:?}",
896                &result.program_counter_change
897            )),
898            Some(ProgramCounterChange::SequenceChange(_)) => {
899                Err("SED instruction should not change sequence flags".to_string())
900            }
901            Some(ProgramCounterChange::DismissAndWait(_)) => Err(
902                "SED instruction should not cause the current sequence's flag to drop".to_string(),
903            ),
904            Some(ProgramCounterChange::Stop(addr)) => Err(format!(
905                "SED instruction execution stopped at address {addr:?}",
906            )),
907        }
908    }
909
910    fn simulate_sed_no_alarm(
911        ctx: &Context,
912        e: Unsigned36Bit, // initial content of E register
913        j: Unsigned6Bit,
914        xj: Signed18Bit,           // initial value of Xj
915        initial_tj: Unsigned36Bit, // initial content of Tj
916        p: Address,
917        inst: &SymbolicInstruction,
918    ) -> bool {
919        match simulate_sed(ctx, e, j, xj, initial_tj, p, inst) {
920            Err(err) => {
921                panic!("{}", err);
922            }
923            Ok((Some(alarm), _)) => {
924                panic!("SED instruction unexpectedly raised an alarm {alarm}");
925            }
926            Ok((None, skip)) => skip,
927        }
928    }
929
930    /// This test is based on example 1 for SED on page 3-36 of the
931    /// Users Handbook.   It's the skip-occurs case.
932    #[test]
933    fn test_sed_example_1_direct_skip() {
934        const INITIAL_E_REG_VALUE: Unsigned36Bit = u36!(0o444_444_444_444); // contents of E register
935        let context = make_ctx();
936        let j = u6!(0);
937        let skipped = simulate_sed_no_alarm(
938            &context,
939            INITIAL_E_REG_VALUE,
940            j,
941            Signed18Bit::ZERO,       // j=0, meaning use X₀.  X₀ must always be 0.
942            u36!(0o555_555_555_555), // content of Tj (which differs from E)
943            Address::from(u18!(0o100)), // P
944            &SymbolicInstruction {
945                held: false,
946                configuration: Unsigned5Bit::ZERO,
947                opcode: Opcode::Sed,
948                index: j,
949                operand_address: OperandAddress::direct(Address::from(u18!(0o100))),
950            },
951        );
952        assert!(
953            skipped,
954            "SED instruction failed to skip when it should have"
955        );
956    }
957
958    /// This test is based on example 1 for SED on page 3-36 of the
959    /// Users Handbook.   It's the no-skip-occurs case.
960    #[test]
961    fn test_sed_example_1_direct_noskip() {
962        const INITIAL_E_REG_VALUE: Unsigned36Bit = u36!(0o444_444_444_444); // contents of E register
963        let context = make_ctx();
964        let j = u6!(0);
965        let skipped = simulate_sed_no_alarm(
966            &context,
967            INITIAL_E_REG_VALUE,
968            j,
969            Signed18Bit::ZERO,   // j=0, meaning use X₀.  X₀ must always be 0.
970            INITIAL_E_REG_VALUE, // content of Tj (which is the same as E)
971            Address::from(u18!(0o100)), // P
972            &SymbolicInstruction {
973                held: false,
974                configuration: Unsigned5Bit::ZERO,
975                opcode: Opcode::Sed,
976                index: j,
977                operand_address: OperandAddress::direct(Address::from(u18!(0o100))),
978            },
979        );
980        assert!(!skipped, "SED instruction skipped when it should not have");
981    }
982
983    /// This test is based on example 2 for SED on page 3-36 of the
984    /// Users Handbook.   It's the skip-occurs case.
985    #[test]
986    fn test_sed_example_2_direct_skip() {
987        const INITIAL_E_REG_VALUE: Unsigned36Bit = u36!(0o555_555_444_444); // contents of E register
988        let context = make_ctx();
989        let j = u6!(0);
990        let skipped = simulate_sed_no_alarm(
991            &context,
992            // In configuration 2, SED compares the R(E)( with L(Tj).
993            // So these don't match (even though the value of Tj and
994            // the value of E are the same).
995            INITIAL_E_REG_VALUE,
996            j,
997            Signed18Bit::ZERO,   // j=0, meaning use X₀.  X₀ must always be 0.
998            INITIAL_E_REG_VALUE, // content of Tj (which is the same as E)
999            Address::from(u18!(0o100)), // P
1000            &SymbolicInstruction {
1001                held: false,
1002                configuration: u5!(2),
1003                opcode: Opcode::Sed,
1004                index: j,
1005                operand_address: OperandAddress::direct(Address::from(u18!(0o100))),
1006            },
1007        );
1008        // L(0o555_555_444_444) != R(0o555_555_444_444), so a skip should occur.
1009        assert!(skipped, "SED instruction should have skipped");
1010    }
1011
1012    /// This test is based on example 2 for SED on page 3-36 of the
1013    /// Users Handbook.  It's the no-skip-occurs case (because the
1014    /// values being compared are equal).
1015    #[test]
1016    fn test_sed_example_2_direct_noskip() {
1017        const INITIAL_E_REG_VALUE: Unsigned36Bit = u36!(0o070_070_333_333); // contents of E register
1018        let context = make_ctx();
1019        let j = u6!(0);
1020        let skipped = simulate_sed_no_alarm(
1021            &context,
1022            // In configuration 2, SED compares the R(E)( with L(Tj).
1023            // These match.
1024            INITIAL_E_REG_VALUE,
1025            j,
1026            Signed18Bit::ZERO,       // j=0, meaning use X₀.  X₀ must always be 0.
1027            u36!(0o333_333_020_020), // content of Tj (of which R is the same as L(E))
1028            Address::from(u18!(0o100)), // P
1029            &SymbolicInstruction {
1030                held: false,
1031                configuration: u5!(2),
1032                opcode: Opcode::Sed,
1033                index: j,
1034                operand_address: OperandAddress::direct(Address::from(u18!(0o100))),
1035            },
1036        );
1037        // L(Tj) == R(E), so there should be a skip.
1038        // L(0o333_333_020_020) == R(0o070_070_333_333), so a skip should occur.
1039        assert!(!skipped, "SED instruction should not have skipped");
1040    }
1041}