base/
types.rs

1/// The TX-2 uses 36-bit words.  We use `Unsigned36Bit` (defined in
2/// onescomplement/unsigned.rs) to represent this.  As stored in
3/// memory, there are two additional bits; these are implemented in
4/// the CPU emulation, not here.
5use std::cmp::Ordering;
6use std::fmt::{Debug, Display, Error, Formatter, Octal};
7use std::hash::{Hash, Hasher};
8
9#[cfg(test)]
10use test_strategy::Arbitrary;
11
12use super::onescomplement::error::ConversionFailed;
13use super::onescomplement::signed::{Signed5Bit, Signed6Bit, Signed18Bit};
14use super::onescomplement::unsigned::{Unsigned6Bit, Unsigned18Bit, Unsigned36Bit};
15use super::onescomplement::{Signum, WordCommon};
16
17/// The `IndexBy` trait implements address arithmetic (adding a signed
18/// or unsigned value to an address).
19///
20/// On page 12-9, Volume 2 of the Technical Manual contains some
21/// wording that I interpret to mean that address indexation arithmetic
22/// wraps:
23///
24/// > While the sum of a base address in N₂,₁ and an index register in
25/// > X is being formed betweek PK¹³ and PK²², the X Adder carry
26/// > circuit is forced into a "set" condition.  This causes the sum
27/// > of an 18 bit number and its 18 bit ONE's complement to be all
28/// > ZEROS, rather than all ONES, if this sum should be formed.  The
29/// > comuted address of an operand, deferred address, or next
30/// > instruction then becomes the first register of the S Memory
31/// > (address 0) rather than the last register of the V mMemory
32/// > (address 377 777 (octal)), when, for example, the base address
33/// > is 000 004 and the index is 777 773.  The logic for obtaining
34/// > this result simply uses the PK¹³ᵝ 0.4 microsecond time level to
35/// > set the X Adder carry circuit at the time that XAC would
36/// > ordinarily have been used to clear it.
37///
38/// `IndexBy` is also used to increment the program counter, and on
39/// the real TX2 this was done by a special circuit, not an adder.
40/// Volume 2 of the Technical Manual (section 12-2.3 "P REGISTER
41/// DRIVER LOGIC") states,
42///
43/// > Information can be transferred into the P register only from the
44/// > X Adder.  In addition to this single transfer path, ther P
45/// > register has a counter which can index the contents of the P
46/// > register by one.  Note that count circuit does not alter the
47/// > contents of P₂.₉
48pub trait IndexBy<T> {
49    fn index_by(&self, delta: T) -> Address;
50}
51
52/// An address has 17 normal value bits.  There are 18 bits for the
53/// operand base address in the instruction word, but the topmost bit
54/// signals a deferred (i.e. indirect) access, so we should never see
55/// a memory access to an address with the `0o400_000` bit set.
56///
57/// The program counter can also have its top bit set.  This signals
58/// that in the TX-2 hardware, the associated sequence should be
59/// traced via Trap 42.
60///
61/// The Xj (index) registers form an 18-bit ring and are described on
62/// page 3-68 of the User Handbook (section 3-3.1) as being signed
63/// integers.  Yet P (which also holds an address) is described on the
64/// same page as being a positive integer.  Therefore when performing
65/// address arithmetic, we sometimes need to convert index register
66/// values to the [`Signed18Bit`] type.
67#[cfg_attr(test, derive(Arbitrary))]
68#[derive(Clone, Copy)]
69pub struct Address(Unsigned18Bit);
70
71/// Placeholders (saved sequence instruction pointers in the index
72/// registers) use bit 2.9 to indicate that sequence switches to
73/// marked sequences should trap to sequence 42.  This means that the
74/// mark bit needs to be retained in the program counter (P register)
75/// so that the sequence is still "marked" after it has run.
76pub const PLACEHOLDER_MARK_BIT: Unsigned18Bit = Unsigned18Bit::MAX.and(1u32 << 17); // bit 2.9
77
78impl Address {
79    pub const ZERO: Address = Address(Unsigned18Bit::ZERO);
80    pub const MAX: Address = Address(Unsigned18Bit::MAX);
81
82    #[must_use]
83    pub const fn new(a: Unsigned18Bit) -> Address {
84        Address(a)
85    }
86
87    /// Apply a bitmask to an address.  This could also be done via
88    /// [`std::ops::BitAnd`], but trait implementations cannot be
89    /// const.  Implementing this const methods allows us to form
90    /// address constants at compile time with code like `Address::MAX
91    /// & 0o03400`.
92    #[must_use]
93    pub const fn and(&self, mask: u32) -> Address {
94        Address(self.0.and(mask)) // use same hack in Unsigned18Bit.
95    }
96
97    /// Extract the placeholder mark bit.
98    #[must_use]
99    pub fn mark_bit(&self) -> Unsigned18Bit {
100        self.0 & PLACEHOLDER_MARK_BIT
101    }
102
103    /// The placeholder phrasing here comes from the "TRAP 42"
104    /// features.  But the high bit in the operand address field in an
105    /// instruction also marks the operand as using deferred
106    /// addressing.  We should choose more neutral naming to avoid
107    /// confusion.
108    #[must_use]
109    pub fn is_marked_placeholder(&self) -> bool {
110        self.0 & PLACEHOLDER_MARK_BIT != 0_u16
111    }
112
113    /// Split an address into its bottom 17 bits (which corresponds to
114    /// an actual memory register on the TX-2) and a "mark" bit.  The
115    /// "mark" bit is variously used to signal deferred addressing
116    /// when fetching operands, and to flag a sequence for tracing
117    /// with trap 42 (in this context is is called a "placeholder mark
118    /// bit").  The `split` method is the opposite of `join`.
119    #[must_use]
120    pub fn split(&self) -> (Unsigned18Bit, bool) {
121        let without_mark: Unsigned18Bit = self.0 & !PLACEHOLDER_MARK_BIT;
122        (without_mark, self.is_marked_placeholder())
123    }
124
125    /// Form an address from the bottom 17 bits of `addr` and a
126    /// possilbe mark bit, `mark`.  If `mark` is false, the resulting
127    /// address will have a zero-valued top bit, no matter what is in
128    /// `addr`.  This is the opposite of `split`.
129    #[must_use]
130    pub fn join(addr: Unsigned18Bit, mark: bool) -> Address {
131        let markbit: Unsigned18Bit = if mark {
132            PLACEHOLDER_MARK_BIT
133        } else {
134            Unsigned18Bit::ZERO
135        };
136        let addr_without_mark: Unsigned18Bit = addr & !PLACEHOLDER_MARK_BIT;
137        Address::from(markbit | addr_without_mark)
138    }
139
140    /// Computes the address following the current address.  Used,
141    /// among other things, to increment the program counter.
142    ///
143    /// The simulator relies on the fact that (as in the hardware TX-2
144    /// P register) this calculation will wrap from `0o377_777` to 0.
145    #[must_use]
146    pub fn successor(&self) -> Address {
147        self.index_by(1_u8)
148    }
149}
150
151impl From<Unsigned18Bit> for Address {
152    fn from(a: Unsigned18Bit) -> Address {
153        Address(a)
154    }
155}
156
157impl From<Address> for Unsigned18Bit {
158    fn from(addr: Address) -> Self {
159        addr.0
160    }
161}
162
163impl From<&Address> for Unsigned18Bit {
164    fn from(addr: &Address) -> Self {
165        addr.0
166    }
167}
168
169impl From<&Address> for Unsigned36Bit {
170    fn from(addr: &Address) -> Self {
171        addr.0.into()
172    }
173}
174
175impl From<Address> for Unsigned36Bit {
176    fn from(addr: Address) -> Self {
177        Unsigned36Bit::from(u32::from(addr))
178    }
179}
180
181impl TryFrom<Unsigned36Bit> for Address {
182    type Error = ConversionFailed;
183    fn try_from(n: Unsigned36Bit) -> Result<Address, ConversionFailed> {
184        let a: Unsigned18Bit = Unsigned18Bit::try_from(n)?;
185        Ok(Address::from(a))
186    }
187}
188
189impl IndexBy<u8> for Address {
190    fn index_by(&self, index: u8) -> Address {
191        let offset: Unsigned18Bit = index.into();
192        let (address, mark) = self.split();
193        Address::join(address.wrapping_add(offset) & !PLACEHOLDER_MARK_BIT, mark)
194    }
195}
196
197fn unsigned_idx_impl(base: Address, delta: Unsigned18Bit) -> Result<Address, ConversionFailed> {
198    let (current, mark) = base.split();
199    let physical = current.wrapping_add(delta) & !PLACEHOLDER_MARK_BIT;
200    Ok(Address::join(physical, mark))
201}
202
203fn signed_idx_impl(base: Address, delta: Signed18Bit) -> Result<Address, ConversionFailed> {
204    let (current, mark) = base.split();
205    let abs_delta: Unsigned18Bit = Unsigned18Bit::try_from(i32::from(delta.abs()))?;
206    let physical = match delta.signum() {
207        Signum::Zero => current,
208        Signum::Positive => current.wrapping_add(abs_delta),
209        Signum::Negative => current.wrapping_sub(abs_delta),
210    } & !PLACEHOLDER_MARK_BIT;
211    Ok(Address::join(physical, mark))
212}
213
214impl IndexBy<Signed5Bit> for Address {
215    fn index_by(&self, delta: Signed5Bit) -> Address {
216        signed_idx_impl(*self, Signed18Bit::from(delta)).unwrap()
217    }
218}
219
220impl IndexBy<Signed6Bit> for Address {
221    fn index_by(&self, delta: Signed6Bit) -> Address {
222        signed_idx_impl(*self, Signed18Bit::from(delta)).unwrap()
223    }
224}
225
226impl IndexBy<Signed18Bit> for Address {
227    fn index_by(&self, delta: Signed18Bit) -> Address {
228        signed_idx_impl(*self, delta).unwrap()
229    }
230}
231
232impl IndexBy<Unsigned18Bit> for Address {
233    fn index_by(&self, delta: Unsigned18Bit) -> Address {
234        unsigned_idx_impl(*self, delta).unwrap()
235    }
236}
237
238#[test]
239fn index_by_5bit_quantity() {
240    let start: Address = Address::from(Unsigned18Bit::from(0o4000_u16));
241    let up: Signed5Bit = Signed5Bit::try_from(6_i8).unwrap();
242    assert_eq!(
243        start.index_by(up),
244        Address::from(Unsigned18Bit::from(0o4006_u16))
245    );
246
247    let down: Signed5Bit = Signed5Bit::try_from(-4_i8).unwrap();
248    assert_eq!(
249        start.index_by(down),
250        Address::from(Unsigned18Bit::from(0o3774_u16))
251    );
252}
253
254impl TryFrom<u32> for Address {
255    type Error = ConversionFailed;
256    fn try_from(n: u32) -> Result<Address, ConversionFailed> {
257        let value = Unsigned18Bit::try_from(n)?;
258        Ok(Address::from(value))
259    }
260}
261
262impl From<Address> for u32 {
263    fn from(a: Address) -> u32 {
264        u32::from(Unsigned18Bit::from(a))
265    }
266}
267
268impl From<&Address> for u32 {
269    fn from(a: &Address) -> u32 {
270        u32::from(Unsigned18Bit::from(*a))
271    }
272}
273
274impl Default for Address {
275    fn default() -> Address {
276        Address::from(Unsigned18Bit::from(0_u8))
277    }
278}
279
280impl Display for Address {
281    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
282        // Always display as octal.
283        write!(f, "{:>08o}", self.0)
284    }
285}
286
287impl Octal for Address {
288    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
289        let val = self.0;
290        Octal::fmt(&val, f) // delegate to u32's implementation
291    }
292}
293
294impl Debug for Address {
295    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
296        // Always display as octal.
297        write!(f, "{:>08o}", self.0)
298    }
299}
300
301impl Ord for Address {
302    fn cmp(&self, other: &Self) -> Ordering {
303        self.0.cmp(&other.0)
304    }
305}
306
307impl PartialOrd for Address {
308    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
309        Some(self.cmp(other))
310    }
311}
312
313impl PartialEq for Address {
314    fn eq(&self, other: &Self) -> bool {
315        self.0.eq(&other.0)
316    }
317}
318
319impl Hash for Address {
320    fn hash<H>(&self, h: &mut H)
321    where
322        H: Hasher,
323    {
324        self.0.hash(h);
325    }
326}
327
328impl PartialEq<u32> for Address {
329    fn eq(&self, other: &u32) -> bool {
330        let v: u32 = self.0.into();
331        v.eq(other)
332    }
333}
334impl Eq for Address {}
335
336pub type SequenceNumber = Unsigned6Bit;
337
338#[test]
339fn test_sequence_numnber() {
340    const ZERO: SequenceNumber = SequenceNumber::ZERO;
341    const ONE: SequenceNumber = SequenceNumber::ONE;
342
343    // Verify that left-shift works (becauise we will need it when
344    // handling flag raise/lower operations).
345    assert_eq!(SequenceNumber::try_from(2_u8).unwrap(), ONE << ONE);
346
347    // Verify that comparisons work (because we will need those when
348    // figuring out whether a raised flag should cause a sequence
349    // change).
350    assert!(ONE > ZERO);
351    assert!(ZERO < ONE);
352    assert_eq!(ONE, ONE);
353    assert_eq!(ZERO, ZERO);
354    assert!(ONE != ZERO);
355    assert!(ZERO != ONE);
356
357    // When SequenceNumber is Unsigned6Bit, SequenceNumber::MAX should be 0o77.
358    // assert_eq!(SequenceNumber::MAX, SequenceNumber::try_from(0o77_u8).unwrap());
359}