base/
instruction.rs

1//! Binary and symbolic representations of TX-2 instructions.
2//!
3//! A TX-2 instruction occupies 36 bits.  The 36 bits look like this
4//! (least significant bit on the right, bits numbered 0 to 35 in
5//! decimal):
6//!
7//! |Hold |Configuration| Opcode|Index  |Defer|Operand Memory Address|
8//! |-----|-------------|-------|-------|-----|----------------------|
9//! |1 bit|  5 bits     |6 bits |6 bits |1 bit|      17 bits         |
10//! |(35) | (30-34)     |(24-29)|(18-23)|     |       (0-16)         |
11//!
12//! This diagram is taken from section 6-2.1 "INSTRUCTION WORDS" of the
13//! TX-2 Users Handbook (page 6-4, being the 157th page of my PDF file).
14//!
15//! There is a similar diagram in Fig. 5 (p. 11) of "A Functional
16//! Description of the Lincoln TX-2 Computer" by John M. Frankovitch
17//! and H. Philip Peterson, but that is older and, I believe,
18//! inaccurate in the width of the configuration field for example.
19//! Section 6-2.1 of the User Handbook confirms that the configuration
20//! "syllable" is 5 bits.
21//!
22//! Table 7-2 in the user guide defines values of `cf` for the range 0
23//! to 037 inclusive in terms of the standard contents that they fetch
24//! from the F-memory.
25//!
26//! In the programming examples (6M-5780, page 6) we have
27//!
28//! ```tx2
29//! 34 21 00 377,751     ³⁴SPF 377,751
30//! ```
31//!
32//! Here, the operand memory address is 0377751.  The index is 00.
33//! The opcode is `SPF`, octal 21.  The top 6 bits are 034, or 11100
34//! binary.  The user guide states that `SPF` is not configurable, so
35//! presumably the configuration field simply specifies the address in
36//! F-memory that we will write to.
37//!
38//! Later in the same page we have
39//!
40//! ```tx2
41//! 74 11 71 377,762    ³⁴RSX⁷¹  377,762
42//! ```
43//!
44//! It seems that the conventions for specifyign index registers
45//! changed between the writing of that memo and the finalisation of
46//! the syntax for the M4 assembler, and so in the 1963 assembler
47//! input format, this would be:
48//!
49//! ```tx2
50//! 74 11 71 377,762    ³⁴RSX₇₁  377,762
51//! ```
52//!
53//! RSX is opcode 11.   Hence the top 6 bits are  074 = 111100 binary,
54//! Of those only 034=11100 binary seem to be configuration code.  But
55//! the hold bit  seems to be set without this  being indicated in the
56//! symbolic form of the instruction.  Perhaps this was something that
57//! changed after the writing of the  memo; certainly `RSX` is not one
58//! of the instruction listed in section 4-3.2 with a default-hold bit
59//! (TSD, JNX, JPX).
60
61use std::fmt::{self, Debug, Display, Formatter};
62
63#[cfg(test)]
64use test_strategy::{Arbitrary, proptest};
65
66use super::bitselect::{BitPos, BitSelector};
67use super::prelude::*;
68use super::subword;
69mod format;
70
71pub const DEFER_BIT: Unsigned36Bit = Unsigned36Bit {
72    bits: 0o400_000_u64,
73};
74
75/// While examples usually choose 0 as the non-existent bit position,
76/// the index portion of the instruction word (which encodes the
77/// quarter and bit position for SKM instructions) stores two bits of
78/// quarter number and four bits of bit position, meaning that the
79/// possible range of bit positions is 0..=15 (decimal).
80#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
81pub enum NonexistentBitPos {
82    Zero,
83    Thirteen,
84    Fourteen,
85    Fifteen,
86}
87
88#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
89pub enum SkmBitPos {
90    Value(BitPos), // bits 1 to 9 inclusive
91    Meta,          // bit 10
92    Parity,        // bit 11 (can be tested but not set)
93    ParityCircuit, // bit 12 (can be tested but not set)
94
95    /// Bit zero of any quarter is non-existent.  The interpretation
96    /// of bit 0 is explained in the documentation for SKM (Users
97    /// Handbook, page 3-34:
98    ///
99    /// If a non-existent bit is selected, e.g. bit 0.0,1.0,2.0,3.0
100    /// for example, Unconditional Skips (SKU) and Rotate (CYR) will
101    /// still work, but "makes" will do nothing, and conditional skips
102    /// will not skip.
103    Nonexistent(NonexistentBitPos),
104}
105
106impl Display for SkmBitPos {
107    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
108        match self {
109            SkmBitPos::Nonexistent(NonexistentBitPos::Zero) => f.write_str("0"),
110            SkmBitPos::Value(bitpos) => write!(f, "{bitpos}"),
111            SkmBitPos::Meta => f.write_str("10"),
112            SkmBitPos::Parity => f.write_str("11"),
113            SkmBitPos::ParityCircuit => f.write_str("12"),
114            SkmBitPos::Nonexistent(NonexistentBitPos::Thirteen) => f.write_str("13"),
115            SkmBitPos::Nonexistent(NonexistentBitPos::Fourteen) => f.write_str("14"),
116            SkmBitPos::Nonexistent(NonexistentBitPos::Fifteen) => f.write_str("15"),
117        }
118    }
119}
120
121/// `SkmBitSelector` is primarily for use with the SKM
122/// instruction which can access bits 1-9 of quarters, plus the meta
123/// and parity bits (of the full word).  Hence the wider range.  See
124/// the [`index_address_to_bit_selection`] function.
125#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
126pub struct SkmBitSelector {
127    pub quarter: Quarter,
128    pub bitpos: SkmBitPos,
129}
130
131/// Render the bit selector in the form q.b.  Unfortunately the TX2
132/// documentation is inconsistent in how the bit number is formatted.
133/// Page 3-34 of the User Handbook clearly says "Bit Numbers are
134/// interpreted as Decimal".
135///
136/// However, section 5-5.2 of the Users Handbook provides a listing of
137/// the standard reader leader.  The instruction at location 17 is
138/// shown as
139///
140/// ```TX2
141/// INSTRUCTION              NUMERICAL FORM (OCTAL)
142/// ²MKZ₄.₁₂ 400011          |021712 400011|
143/// ```
144///
145/// Section 5-5.2 of the Users Handbook also shows an instruction in
146/// Plugboard A at address 0377775:
147///
148/// ```TX2
149/// INSTRUCTION              NUMERICAL FORM (OCTAL)
150/// ³⁰SKN₄.₁₂ 377744         |301712 377744|
151/// ```
152///
153/// In both our examples above, quarter 3 has the value 0712 octal.
154/// This encodes the value generated by the 4.12 bit selector (as well
155/// as the bottom 3 bits of the opcode).
156///
157/// `SKN` is shorthand for `³⁰SKM` and `MKZ` is shorthand for `²SKM`.
158/// The opcode for `SKM` is 017.  In the TX-2's instruction word, the
159/// index bits are used to store the bit selector for the SKN
160/// instruction.  In the TX-2's convention these are bits 3.1 to 3.6
161/// inclusive.  That is, bits 19 through 24 (counting from 0 as the
162/// least significant):
163///
164/// | Q3 Bit     | Interpretation     | Octal | Meaning                 |
165/// | ---------- | --------------     | ---   | ----------------------- |
166/// | 3.1 to 3.4 | Bit number         | 012   | 10 decimal, the metabit |
167/// | 3.5 to 3.6 | Quarter number     | 000   | Q4                      |
168/// | 3.7 to 3.9 | Low bits of opcode | 007   | Part of 017, for SKM    |
169///
170/// However, for the moment we will adopt the decimal convention since
171/// it is in wider use in the documentation.
172///
173impl Display for SkmBitSelector {
174    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
175        write!(f, "{}.{}", self.quarter, self.bitpos)
176    }
177}
178
179impl SkmBitSelector {
180    #[must_use]
181    pub fn to_value_bit(self) -> Option<BitSelector> {
182        match self.bitpos {
183            SkmBitPos::Value(b) => Some(BitSelector {
184                quarter: self.quarter,
185                bitpos: b,
186            }),
187            _ => None,
188        }
189    }
190}
191
192/// Convert the index address field of an SKM instruction into a
193/// `BitSelector` struct describing which bit we will operate on.
194#[must_use]
195pub fn index_address_to_bit_selection(index_address: Unsigned6Bit) -> SkmBitSelector {
196    let j: u8 = u8::from(index_address);
197    let bitpos: SkmBitPos = match j & 0b1111_u8 {
198        0 => SkmBitPos::Nonexistent(NonexistentBitPos::Zero),
199        1 => SkmBitPos::Value(BitPos::B1),
200        2 => SkmBitPos::Value(BitPos::B2),
201        3 => SkmBitPos::Value(BitPos::B3),
202        4 => SkmBitPos::Value(BitPos::B4),
203        5 => SkmBitPos::Value(BitPos::B5),
204        6 => SkmBitPos::Value(BitPos::B6),
205        7 => SkmBitPos::Value(BitPos::B7),
206        8 => SkmBitPos::Value(BitPos::B8),
207        9 => SkmBitPos::Value(BitPos::B9),
208        10 => SkmBitPos::Meta,
209        11 => SkmBitPos::Parity,
210        12 => SkmBitPos::ParityCircuit,
211        13 => SkmBitPos::Nonexistent(NonexistentBitPos::Thirteen),
212        14 => SkmBitPos::Nonexistent(NonexistentBitPos::Fourteen),
213        15 => SkmBitPos::Nonexistent(NonexistentBitPos::Fifteen),
214        _ => unreachable!(),
215    };
216    SkmBitSelector {
217        quarter: match (j >> 4) & 0b11 {
218            0 => Quarter::Q4,
219            1 => Quarter::Q1,
220            2 => Quarter::Q2,
221            3 => Quarter::Q3,
222            _ => unreachable!(),
223        },
224        bitpos,
225    }
226}
227
228/// A TX-2 Instruction.
229#[derive(Clone, Copy)]
230pub struct Instruction(Unsigned36Bit);
231
232impl Instruction {
233    /// Returns an unspecified invalid instruction.
234    #[must_use]
235    pub fn invalid() -> Instruction {
236        Instruction(Unsigned36Bit::ZERO)
237    }
238
239    #[must_use]
240    pub fn bits(&self) -> Unsigned36Bit {
241        self.0
242    }
243}
244
245impl From<Unsigned36Bit> for Instruction {
246    fn from(w: Unsigned36Bit) -> Instruction {
247        Instruction(w)
248    }
249}
250
251impl From<Instruction> for Unsigned36Bit {
252    fn from(inst: Instruction) -> Unsigned36Bit {
253        inst.0
254    }
255}
256
257impl Debug for Instruction {
258    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
259        let lhs = match SymbolicInstruction::try_from(self) {
260            Ok(sym) => format!("{sym}"),
261            Err(e) => format!("(invalid instruction: {e})"),
262        };
263        write!(f, "{:<40} {}", lhs, self.0)
264    }
265}
266
267/// The `Inst` trait provides a way to extract the various fields
268/// within an instruction.
269pub trait Inst {
270    /// Obtain the value of the "hold" bit.
271    fn is_held(&self) -> bool;
272
273    /// Fetch the configuration field of the instruction.  This is
274    /// used as an index to fetch a system configuration from the
275    /// F-memory.  Table 7-2 in the TX-2 User Handbook explains what
276    /// data exists in the F-memory in the standard configuration.
277    ///
278    /// The meaning of the values fetched from the F-memory (and thus,
279    /// the significance of the configuration values of an
280    /// instruction) is explained in Volume 2 (pages 12-19) of the
281    /// Technical manual.  Some instructions (JMP, for example) use
282    /// this field differently.
283    fn configuration(&self) -> Unsigned5Bit;
284
285    /// indexation is actually a signed operation (see the [`IndexBy`]
286    /// trait) but index addresses are shown as positive numbers
287    /// (e.g. 77) in assembler source code.
288    fn index_address(&self) -> Unsigned6Bit;
289
290    /// The opcode of the instruction.
291    fn opcode_number(&self) -> u8;
292
293    /// The value of the defer bit from the operand address field.
294    fn is_deferred_addressing(&self) -> bool;
295
296    /// Fetches the operand address with the defer bit (if set) in bit
297    /// position 17.
298    fn operand_address_and_defer_bit(&self) -> Unsigned18Bit;
299
300    fn operand_address(&self) -> OperandAddress;
301}
302
303impl Inst for Instruction {
304    fn is_held(&self) -> bool {
305        const HOLD_BIT: u64 = 1_u64 << 35;
306        !(self.0 & HOLD_BIT).is_zero()
307    }
308
309    fn configuration(&self) -> Unsigned5Bit {
310        let val: u8 = u8::try_from((self.0 >> 30) & 31_u64).unwrap();
311        Unsigned5Bit::try_from(val).unwrap()
312    }
313
314    fn opcode_number(&self) -> u8 {
315        u8::try_from((self.0 >> 24) & 63_u64).unwrap()
316    }
317
318    fn index_address(&self) -> Unsigned6Bit {
319        Unsigned6Bit::try_from((self.0 >> 18) & 63_u64).unwrap()
320    }
321
322    fn is_deferred_addressing(&self) -> bool {
323        self.0 & DEFER_BIT != 0
324    }
325
326    fn operand_address(&self) -> OperandAddress {
327        let bits: Unsigned18Bit = subword::right_half(self.0);
328        OperandAddress::from_raw_bits(bits)
329    }
330
331    fn operand_address_and_defer_bit(&self) -> Unsigned18Bit {
332        let bits: u32 = u32::try_from(self.0 & 0o777_777).unwrap();
333        Unsigned18Bit::try_from(bits).unwrap() // range already checked above.
334    }
335}
336
337/// `Opcode` enumerates all the valid TX-2 opcodes.  These values are
338/// taken from the User Handbook.  Volume 3 of the Technical Manual
339/// (page 1-5-3) describes opcodes 00, 01, 02, 03, 04 (mentioning bit
340/// 2.8 of N being in state 1), 13, 23, 33, 45, 50, 51, 52, 53, 63, 73
341/// as being undefined.
342///
343/// Different copies of the User Handbook differ in the description of
344/// opcodes 0o44 and 0o45.
345#[repr(u8)]
346#[cfg_attr(test, derive(Arbitrary))]
347#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
348pub enum Opcode {
349    // opcode 1 is unused
350    // opcode 2 may be XEQ, but no documentation on this.
351    // opcode 3 is unused
352    /// Opcode 04 (which is sometimes known as OPR) is used to encode
353    /// two kinds of instructions, IOS and AOP.
354    ///
355    /// IOS is the I/O select instruction and is described in the TX-2
356    /// Users Handbook, section 4-3.5.
357    ///
358    /// Bits 2.7 and 2.8 of the instruction work determine whether an
359    /// OPR instruction is IOS or AOP (or invalid).
360    ///
361    /// AOP is (apparently, documentation is a bit thin) the
362    /// Arithmetic Operation instruction.  This operates only on the
363    /// Arithmetic unit (that is, it makes no memory reference).
364    ///
365    /// See Technical Manual Vol 2 section 10-2.5.3 (AOP
366    /// instructions), Technical Manual Vol 3 section 16-5.1 (OPR (04)
367    /// AOP, see physical PDF page number 45).  Section 16-5.1 claims
368    /// that AOP is described in detail in chapter 4 of the Technical
369    /// Manual.
370    Opr = 0o4,
371    Jmp = 0o5,
372    Jpx = 0o6,
373    Jnx = 0o7,
374    Aux = 0o10,
375    Rsx = 0o11,
376    Skx = 0o12,
377    // opcode 0o13 = 11 is undefined (unused).
378    Exx = 0o14,
379    Adx = 0o15,
380    Dpx = 0o16,
381    Skm = 0o17,
382    Lde = 0o20,
383    Spf = 0o21,
384    Spg = 0o22,
385    // opcode 0o23 = 19 is undefined (unused).
386    Lda = 0o24,
387    Ldb = 0o25,
388    Ldc = 0o26,
389    Ldd = 0o27,
390    Ste = 0o30,
391    Flf = 0o31,
392    Flg = 0o32,
393    // opcode 033 = 27 is unused.
394    Sta = 0o34,
395    Stb = 0o35,
396    Stc = 0o36,
397    Std = 0o37,
398    Ite = 0o40,
399    Ita = 0o41,
400    Una = 0o42,
401    Sed = 0o43,
402
403    /// I have two copies of the User Handbook and they differ in
404    /// their description of opcodes 0o44, 0o45.
405    ///
406    /// In the August 1963 copy, 0o44 is missing and 0045 is JOV.
407    ///
408    /// In the index of the October 1961 copy, 0o44 is JOV and 0o45 is
409    /// JZA (handwritten).  However, in this copy, page 3-32 (which
410    /// describes JPA, JNA, JOV) gives JOV as 0o45.  So I assume this
411    /// is just an error in the index.  This copy does not otherwise
412    /// describe a JZA opcode.
413    Jov = 0o45,
414
415    Jpa = 0o46,
416    Jna = 0o47,
417    // opcode 0o50 = 40 is undefined (unused).
418    // opcode 0o51 = 41 is undefined (unused).
419    // opcode 0o52 = 42 is undefined (unused).
420    // opcode 0o53 = 43 is undefined (unused).
421    Exa = 0o54,
422    Ins = 0o55,
423    Com = 0o56,
424    Tsd = 0o57,
425    Cya = 0o60,
426    Cyb = 0o61,
427    Cab = 0o62,
428    // opcode 0o63 = 51 is unused.
429    Noa = 0o64,
430    Dsa = 0o65,
431    Nab = 0o66,
432    Add = 0o67,
433    Sca = 0o70,
434    Scb = 0o71,
435    Sab = 0o72,
436    // opcode 0o71 = 59 is unused.
437    Tly = 0o74,
438    Div = 0o75,
439    Mul = 0o76,
440    Sub = 0o77,
441}
442
443impl Opcode {
444    #[must_use]
445    pub fn number(&self) -> u8 {
446        *self as u8
447    }
448
449    #[must_use]
450    pub fn hold_is_implicit(&self) -> bool {
451        matches!(self, Opcode::Lde | Opcode::Ite | Opcode::Jpx | Opcode::Jnx)
452    }
453}
454
455impl TryFrom<u8> for Opcode {
456    type Error = DisassemblyFailure;
457    fn try_from(opcode: u8) -> Result<Opcode, DisassemblyFailure> {
458        use Opcode::*;
459        #[allow(clippy::match_same_arms)] // numerical order
460        match opcode {
461            // TODO: change these opcode values to octal.
462            0 | 1 => Err(DisassemblyFailure::InvalidOpcode(opcode)),
463            2 => {
464                // Maybe XEQ?
465                Err(DisassemblyFailure::UnimplementedOpcode(opcode))
466            }
467            3 => Err(DisassemblyFailure::InvalidOpcode(opcode)),
468            0o4 => Ok(Opr),
469            0o5 => Ok(Jmp),
470            0o6 => Ok(Jpx),
471            0o7 => Ok(Jnx),
472
473            0o10 => Ok(Aux),
474            0o11 => Ok(Rsx),
475            0o12 => Ok(Skx),
476            0o13 => Err(DisassemblyFailure::InvalidOpcode(opcode)),
477            0o14 => Ok(Exx),
478            0o15 => Ok(Adx),
479            0o16 => Ok(Dpx),
480            0o17 => Ok(Skm),
481
482            0o20 => Ok(Lde),
483            0o21 => Ok(Spf),
484            0o22 => Ok(Spg),
485            0o23 => Err(DisassemblyFailure::InvalidOpcode(opcode)),
486            0o24 => Ok(Lda),
487            0o25 => Ok(Ldb),
488            0o26 => Ok(Ldc),
489            0o27 => Ok(Ldd),
490
491            0o30 => Ok(Ste),
492            0o31 => Ok(Flf),
493            0o32 => Ok(Flg),
494            0o33 => Err(DisassemblyFailure::InvalidOpcode(opcode)),
495            0o34 => Ok(Sta),
496            0o35 => Ok(Stb),
497            0o36 => Ok(Stc),
498            0o37 => Ok(Std),
499
500            0o40 => Ok(Ite),
501            0o41 => Ok(Ita),
502            0o42 => Ok(Una),
503            0o43 => Ok(Sed),
504            0o44 => Err(DisassemblyFailure::InvalidOpcode(opcode)),
505            0o45 => Ok(Jov),
506            0o46 => Ok(Jpa),
507            0o47 => Ok(Jna),
508
509            0o50..=0o53 => Err(DisassemblyFailure::InvalidOpcode(opcode)),
510            0o54 => Ok(Exa),
511            0o55 => Ok(Ins),
512            0o56 => Ok(Com),
513            0o57 => Ok(Tsd),
514
515            0o60 => Ok(Cya),
516            0o61 => Ok(Cyb),
517            0o62 => Ok(Cab),
518            0o63 => Err(DisassemblyFailure::InvalidOpcode(opcode)),
519            0o64 => Ok(Noa),
520            0o65 => Ok(Dsa),
521            0o66 => Ok(Nab),
522            0o67 => Ok(Add),
523
524            0o70 => Ok(Sca),
525            0o71 => Ok(Scb),
526            0o72 => Ok(Sab),
527            0o73 => Err(DisassemblyFailure::InvalidOpcode(opcode)),
528            0o74 => Ok(Tly),
529            0o75 => Ok(Div),
530            0o76 => Ok(Mul),
531            0o77 => Ok(Sub),
532            _ => Err(DisassemblyFailure::InvalidOpcode(opcode)),
533        }
534    }
535}
536
537/// `OperandAddress` represents the least-significant 18 bits of an
538/// instruction word.  If the top bit is set, this indicates the use
539/// of deferred addressing (i.e. this bit has the same significance as
540/// it does in TX-2 instructions).
541#[cfg_attr(test, derive(Arbitrary))]
542#[derive(Debug, PartialEq, Eq, Clone, Copy)]
543pub struct OperandAddress(Address);
544
545impl Default for OperandAddress {
546    fn default() -> OperandAddress {
547        OperandAddress(Address::ZERO)
548    }
549}
550
551impl OperandAddress {
552    #[must_use]
553    pub fn is_deferred(&self) -> bool {
554        let (_, deferred) = self.0.split();
555        deferred
556    }
557
558    #[must_use]
559    pub fn split(&self) -> (bool, Address) {
560        let (physical, deferred) = self.0.split();
561        (deferred, Address::from(physical))
562    }
563
564    #[must_use]
565    pub fn bits(&self) -> Unsigned18Bit {
566        Unsigned18Bit::from(self.0)
567    }
568
569    #[must_use]
570    pub fn direct(address: Address) -> OperandAddress {
571        let (_, defer) = address.split();
572        assert!(!defer);
573        Self(address)
574    }
575
576    #[must_use]
577    pub fn deferred(address: Address) -> OperandAddress {
578        let (bits, _) = address.split();
579        Self(Address::join(bits, true))
580    }
581
582    fn from_raw_bits(bits: Unsigned18Bit) -> OperandAddress {
583        Self(Address::from(bits))
584    }
585}
586
587/// A TX-2 instruction broken down into its component fields.
588#[cfg_attr(test, derive(Arbitrary))]
589#[derive(Debug, PartialEq, Eq)]
590pub struct SymbolicInstruction {
591    pub held: bool,
592    pub configuration: Unsigned5Bit,
593    pub opcode: Opcode,
594    pub index: Unsigned6Bit,
595    pub operand_address: OperandAddress,
596}
597
598impl SymbolicInstruction {
599    #[must_use]
600    pub fn opcode(&self) -> Opcode {
601        self.opcode
602    }
603}
604
605impl Inst for SymbolicInstruction {
606    fn is_held(&self) -> bool {
607        self.held
608    }
609
610    fn configuration(&self) -> Unsigned5Bit {
611        self.configuration
612    }
613
614    fn index_address(&self) -> Unsigned6Bit {
615        self.index
616    }
617
618    fn opcode_number(&self) -> u8 {
619        self.opcode.number()
620    }
621
622    fn is_deferred_addressing(&self) -> bool {
623        self.operand_address.is_deferred()
624    }
625
626    fn operand_address(&self) -> OperandAddress {
627        self.operand_address
628    }
629
630    fn operand_address_and_defer_bit(&self) -> Unsigned18Bit {
631        self.operand_address.bits()
632    }
633}
634
635impl From<&SymbolicInstruction> for Instruction {
636    fn from(s: &SymbolicInstruction) -> Instruction {
637        let hold_bit: u64 = if s.is_held() { 1 << 35 } else { 0 };
638        let cf_bits: u64 = (u64::from(s.configuration()) & 31_u64) << 30;
639        let op_bits: u64 = (u64::from(s.opcode_number()) & 63) << 24;
640        let index_bits: u64 = (u64::from(s.index_address())) << 18;
641        let address_and_defer_bits: u64 = s.operand_address_and_defer_bit().into();
642        let val: u64 = hold_bit | cf_bits | op_bits | index_bits | address_and_defer_bits;
643        Instruction(Unsigned36Bit::try_from(val).unwrap())
644    }
645}
646
647#[cfg(test)]
648#[proptest]
649fn reversible_disassembly(input: SymbolicInstruction) {
650    let inst: Instruction = Instruction::from(&input);
651    let bits = inst.bits();
652    match SymbolicInstruction::try_from(&inst) {
653        Ok(symbolic_disassembly) => {
654            assert_eq!(symbolic_disassembly, input);
655        }
656        Err(e) => {
657            panic!("input {input:?} assembled to {bits} but that could not be disassembled ({e})");
658        }
659    }
660}
661
662/// Signals that an [`Instruction`] could not be converted to a
663/// [`SymbolicInstruction`].
664#[derive(PartialEq, Eq)]
665pub enum DisassemblyFailure {
666    /// The opcode field of the instruction word does not correspond
667    /// to a known opcode.
668    InvalidOpcode(u8),
669
670    /// The opcode field of the instruction word corresponds to an
671    /// operation we know nothing about.  This enumerator shouldn't be
672    /// considered stable; we might remove it entirely.  Currently only
673    /// opcode 2 yields this result (corresponding to "XEQ?"
674    /// hand-written on a copy of the User Handbook).
675    UnimplementedOpcode(u8),
676}
677
678impl Debug for DisassemblyFailure {
679    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
680        let opcode = match self {
681            DisassemblyFailure::InvalidOpcode(n) => {
682                f.write_str("InvalidOpcode")?;
683                n
684            }
685            DisassemblyFailure::UnimplementedOpcode(n) => {
686                f.write_str("UnimplementedOpcode")?;
687                n
688            }
689        };
690        write!(f, "(0{opcode:o})")
691    }
692}
693
694impl TryFrom<&Instruction> for SymbolicInstruction {
695    type Error = DisassemblyFailure;
696    fn try_from(inst: &Instruction) -> Result<SymbolicInstruction, DisassemblyFailure> {
697        Ok(SymbolicInstruction {
698            operand_address: inst.operand_address(),
699            index: inst.index_address(),
700            opcode: Opcode::try_from(inst.opcode_number())?,
701            configuration: inst.configuration(),
702            held: inst.is_held(),
703        })
704    }
705}
706
707/// `disassemble_word` is intended to be used in the emulator's UI to
708/// show what the program counter is pointing at, and to show the next
709/// instruction to be emulated etc.  Since this is not yet implemented,
710/// right now the only callers are unit tests.
711///
712/// # Errors
713///
714/// `DisassemblyFailure` indicates that the word is not a valid
715/// instruction (for example because it contains an invalid opcode).
716#[cfg(test)]
717pub fn disassemble_word(w: Unsigned36Bit) -> Result<SymbolicInstruction, DisassemblyFailure> {
718    let inst: Instruction = Instruction(w);
719    dbg!(inst.operand_address());
720    SymbolicInstruction::try_from(&inst)
721}
722
723#[cfg(test)]
724mod tests {
725    use super::*;
726
727    fn config_value(n: u8) -> Unsigned5Bit {
728        Unsigned5Bit::try_from(n).expect("valid test data")
729    }
730
731    #[test]
732    fn test_instruction_jmp() {
733        let inst = Instruction(Unsigned36Bit::from(0o0_500_377_750_u32));
734        assert_eq!(
735            inst.operand_address(),
736            OperandAddress::direct(Address::from(u18!(0o377_750u32))),
737            "wrong address"
738        );
739        assert!(!inst.is_deferred_addressing(), "wrong dismiss");
740        assert_eq!(inst.index_address(), 0, "wrong index");
741        assert_eq!(inst.opcode_number(), 5, "wrong opcode");
742        assert!(inst.configuration().is_zero(), "wrong cf");
743        assert!(!inst.is_held(), "wrong held");
744    }
745
746    #[test]
747    fn test_instruction_held() {
748        assert!(!Instruction(Unsigned36Bit::ZERO).is_held());
749        assert!(
750            Instruction(
751                Unsigned36Bit::try_from(1_u64 << 35).expect("test data should be in-range")
752            )
753            .is_held()
754        );
755    }
756
757    #[test]
758    fn test_opcode_value_round_trip() {
759        for opcode_number in 0..u8::MAX {
760            if let Ok(opcode) = Opcode::try_from(opcode_number) {
761                assert_eq!(opcode_number, opcode.number());
762            }
763        }
764    }
765
766    #[test]
767    fn test_disassemble_jmp() {
768        assert_eq!(
769            disassemble_word(u36!(0o000_500_377_750)),
770            Ok(SymbolicInstruction {
771                operand_address: OperandAddress::direct(Address::from(u18!(0o_377_750))),
772                index: Unsigned6Bit::ZERO,
773                opcode: Opcode::Jmp,
774                configuration: config_value(0),
775                held: false,
776            })
777        );
778    }
779
780    #[test]
781    fn test_disassemble_jpx() {
782        assert_eq!(
783            disassemble_word(
784                Unsigned36Bit::try_from(0o700_603_377_752_u64)
785                    .expect("test data should be in range")
786            ),
787            Ok(SymbolicInstruction {
788                operand_address: OperandAddress::direct(Address::from(u18!(0o0_377_752))),
789                index: Unsigned6Bit::try_from(3_u8).unwrap(),
790                opcode: Opcode::Jpx,
791                configuration: config_value(24), // -7
792                held: true,
793            })
794        );
795
796        assert_eq!(
797            disassemble_word(
798                Unsigned36Bit::try_from(0o360_601_377_751_u64)
799                    .expect("test data should be in range")
800            ),
801            Ok(SymbolicInstruction {
802                operand_address: OperandAddress::direct(Address::from(u18!(0o0_377_751))),
803                index: Unsigned6Bit::ONE,
804                opcode: Opcode::Jpx,
805                configuration: config_value(30),
806                held: false,
807            })
808        );
809    }
810
811    #[test]
812    fn test_disassemble_dpx() {
813        // This instruction is taken from the code for Leonard
814        // Kleinrock's network simulation, at address 200762.
815        assert_eq!(
816            disassemble_word(u36!(0o011_600_777_605_u64)),
817            Ok(SymbolicInstruction {
818                operand_address: OperandAddress::deferred(Address::from(u18!(0o377_605))),
819                index: Unsigned6Bit::try_from(0_u8).unwrap(),
820                opcode: Opcode::Dpx,
821                configuration: config_value(1),
822                held: false,
823            })
824        );
825    }
826
827    #[test]
828    fn test_assemble_rsx() {
829        // This instruction is taken from position 0o3 of the standard
830        // reader leader on page 5-26 of the Users Handbook.
831        const EXPECTED: u64 = 0o011_154_000_005_u64;
832        let sym = SymbolicInstruction {
833            held: false,
834            configuration: Unsigned5Bit::try_from(1_u8).expect("valid configuraiton field"),
835            opcode: Opcode::Rsx,
836            index: Unsigned6Bit::try_from(0o54_u8).expect("valid index field"),
837            operand_address: OperandAddress::direct(Address::from(u18!(5))),
838        };
839        let got: u64 = u64::from(Instruction::from(&sym).bits());
840        assert_eq!(
841            got, EXPECTED,
842            "Mismatch in assembly of {:?}: expected 0o{:012o}, got 0o{:012o}",
843            &sym, EXPECTED, got
844        );
845    }
846}