cpu/
alarm.rs

1//! TX-2 alarms.
2use std::error::Error;
3use std::fmt::{self, Display, Formatter};
4use std::time::Duration;
5
6use serde::Serialize;
7
8use base::instruction::Instruction;
9use base::prelude::*;
10
11use super::bugreport::{IssueType, bug_report_url};
12use super::diagnostics::{CurrentInstructionDiagnostics, DiagnosticFetcher};
13
14/// A memory read or write failure.
15#[derive(Debug, Clone)]
16pub enum BadMemOp {
17    /// Describes a failure to read from an address.
18    Read(Unsigned36Bit),
19    /// Describes a failure to write to an address.
20    Write(Unsigned36Bit),
21}
22
23impl Display for BadMemOp {
24    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
25        match self {
26            BadMemOp::Read(addr) => write!(f, "memory read from {addr:>013o} failed"),
27            BadMemOp::Write(addr) => write!(f, "memory write to {addr:>013o} failed"),
28        }
29    }
30}
31
32/// Describes whether a particular kind of alarm can be masked.
33#[derive(Debug, PartialEq, Eq)]
34pub enum AlarmMaskability {
35    Maskable,
36    Unmaskable,
37}
38
39/// Describes the kinds of alarm that exist in the TX-2.
40///
41/// Some alarms which cannot occur in the emulator (such as partity
42/// alarms in the X-memory or the STUV-memory) don't have an
43/// enumerator.
44///
45/// These acrronyms are upper case to follow the names in the TX-2
46/// documentation.  The meanings of the values are described in
47/// [`AlarmDetails`].
48#[allow(clippy::upper_case_acronyms)]
49#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord, Serialize)]
50pub enum AlarmKind {
51    PSAL,
52    OCSAL,
53    QSAL,
54    IOSAL,
55    MISAL,
56    ROUNDTUITAL,
57    DEFERLOOPAL,
58    BUGAL,
59}
60
61impl Display for AlarmKind {
62    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
63        f.write_str(match self {
64            AlarmKind::PSAL => "PSAL",
65            AlarmKind::OCSAL => "OCSAL",
66            AlarmKind::QSAL => "QSAL",
67            AlarmKind::IOSAL => "IOSAL",
68            AlarmKind::MISAL => "MISAL",
69            AlarmKind::ROUNDTUITAL => "ROUNDTUITAL",
70            AlarmKind::DEFERLOOPAL => "DEFERLOOPAL",
71            AlarmKind::BUGAL => "BUGAL",
72        })
73    }
74}
75
76impl AlarmKind {
77    /// Indicates whether an alarm can be masked.
78    #[must_use]
79    pub fn maskable(&self) -> AlarmMaskability {
80        match self {
81            AlarmKind::BUGAL | AlarmKind::DEFERLOOPAL | AlarmKind::ROUNDTUITAL => {
82                AlarmMaskability::Unmaskable
83            }
84            _ => AlarmMaskability::Maskable,
85        }
86    }
87
88    #[must_use]
89    pub const fn all_alarm_kinds() -> [AlarmKind; 8] {
90        [
91            AlarmKind::PSAL,
92            AlarmKind::OCSAL,
93            AlarmKind::QSAL,
94            AlarmKind::IOSAL,
95            AlarmKind::MISAL,
96            AlarmKind::ROUNDTUITAL,
97            AlarmKind::DEFERLOOPAL,
98            AlarmKind::BUGAL,
99        ]
100    }
101}
102
103#[derive(Debug)]
104pub struct UnknownAlarmName(String);
105
106impl Display for UnknownAlarmName {
107    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
108        write!(f, "unknown alarm name '{}'", self.0)
109    }
110}
111
112impl Error for UnknownAlarmName {}
113
114impl TryFrom<&str> for AlarmKind {
115    type Error = UnknownAlarmName;
116    fn try_from(s: &str) -> Result<AlarmKind, UnknownAlarmName> {
117        match s {
118            "PSAL" => Ok(AlarmKind::PSAL),
119            "OCSAL" => Ok(AlarmKind::OCSAL),
120            "QSAL" => Ok(AlarmKind::QSAL),
121            "IOSAL" => Ok(AlarmKind::IOSAL),
122            "MISAL" => Ok(AlarmKind::MISAL),
123            "ROUNDTUITAL" => Ok(AlarmKind::ROUNDTUITAL),
124            "DEFERLOOPAL" => Ok(AlarmKind::DEFERLOOPAL),
125            "BUGAL" => Ok(AlarmKind::BUGAL),
126            _ => Err(UnknownAlarmName(s.to_owned())),
127        }
128    }
129}
130
131#[test]
132fn test_alarm_kind_round_trip() {
133    for orig_kind in AlarmKind::all_alarm_kinds() {
134        let name = orig_kind.to_string();
135        match AlarmKind::try_from(name.as_str()) {
136            Ok(k) => {
137                assert_eq!(k, orig_kind);
138            }
139            Err(_) => {
140                panic!("unable to round-trip alarm kind {orig_kind:?}");
141            }
142        }
143    }
144    assert!(AlarmKind::try_from("this is not an alarm name").is_err());
145}
146
147/// Indicates what the emulator was doing when a bug was discovered.
148#[derive(Debug, Clone)]
149pub enum BugActivity {
150    /// Indicates that the emulator was performing I/O at the point a
151    /// bug was discovered.
152    Io,
153    /// Indicates that the emulator was executing an instruction at
154    /// the point a bug was discovered.
155    Opcode,
156    /// Indicates that the emulator was performing an alarm
157    /// manipulation at the point a bug was discovered.
158    AlarmHandling,
159}
160
161/// `AlarmsDetails` variant names are from User's Handbook section
162/// 5-2.2; full names are taken from section 10-2.5.1 (vol 2) of the
163/// Technical Manual.
164///
165/// These acrronyms are upper case to follow the names in the TX-2 documentation.
166///
167/// # Unimplemented Alarms
168///
169/// Alarms we have not implemented:
170///
171/// | Mnemonic | Description | Reason why It's not Included |
172/// | -------- | ----------- | ---------------------------- |
173/// | SYAL1    | Sync System Alarm (see User Handbook page 5-21). | Not yet implemented. |
174/// | SYAL2    | Sync System Alarm (see User Handbook page 5-21). | Not yet implemented. |
175/// | MPAL     | M memory Parity Alarm | Parity errors are not emulated. |
176/// | NPAL     | N memory Parity Alarm | Parity errors are not emulated. |
177/// | FPAL     | F memory Parity Alarm | Parity errors are not emulated. |
178/// | XPAL     | X memory Parity Alarm | Parity errors are not emulated. |
179/// | TSAL     | T memory selection alarm | Indicates overcurrent in the T Memory.  We have no hardware, so no overcurrent. |
180/// | USAL     | U memory selection alarm | Indicates overcurrent in the T Memory.  We have no hardware, so no overcurrent. |
181/// | Mousetrap | Stops the computer when there is a malfunction in the setting of the S-memory flip-flops (or perhaps other reasons chosen by the system maintainers). |  Not required. |
182///
183/// L. G. Roberts memo of 1965-01-07 seems to indicate that another
184/// alarm, SPAL, was introduced later; see
185/// <http://www.bitsavers.org/pdf/mit/tx-2/rcsri.org_library_tx2/TX2-Memos-General_196407.pdf>.
186#[allow(clippy::upper_case_acronyms)]
187#[derive(Debug, Clone)]
188pub enum AlarmDetails {
189    /// P Memory Cycle Selection Alarm.  This fires when we attempt to
190    /// fetch an instruction (but not an operand) from an invalid
191    /// address.
192    PSAL(u32, String),
193
194    /// Operation Code Alarm.  This fires when an instruction word
195    /// containing an undefined operation code is read out of memory.
196    ///
197    /// Section 10-2.5.3 of the TX-2 Technical Manual (Volume 2)
198    /// states that this can also happen when an `AOP` instruction
199    /// specifies an undefined op code in bits N₂.₆-N₂.₁.  An `AOP`
200    /// instruction is has opcode number 4, but with bits N₂.₈=0 and
201    /// N₂.₇=1 (instead of 00 which is the case for an IOS
202    /// instruction).  So far however, we have not found any further
203    /// information about the interpretation of bits N₂.₆-N₂.₁ for
204    /// `AOP`.
205    OCSAL(Instruction, String),
206
207    /// Q Memory Cycle Selecttion Alarm.  Q register (i.e. data fetch
208    /// address) is set to an invalid address.
209    QSAL(Instruction, BadMemOp, String),
210
211    /// In-Out Selection Alarm. I/O Alarm in IOS instruction; device
212    /// broken/maintenance/nonexistent.
213    IOSAL {
214        /// The affected unit (as opposed to the sequence number currently executing).
215        unit: Unsigned6Bit,
216        operand: Option<Unsigned18Bit>,
217        message: String,
218    },
219
220    /// In-Out Miss Indication Alarm.  Fires when some I/O unit has
221    /// missed a data item.  This generally indicates that the program
222    /// is too slow for an I/O device.  For example because it uses
223    /// too many hold bits.
224    MISAL { affected_unit: Unsigned6Bit },
225
226    /// Indicates that something is not implemented in the emulator.
227    /// This alarm didn't exist in the real TX-2.
228    ROUNDTUITAL {
229        explanation: String,
230        bug_report_url: &'static str,
231    },
232
233    /// Loop in deferred addressing (detection of this is not a
234    /// feature of the TX-2, this occurs only in the emulator).
235    DEFERLOOPAL {
236        /// address is some address within the loop.
237        address: Unsigned18Bit,
238    },
239
240    /// There is a bug in the simulator.
241    BUGAL {
242        /// What were we doing?
243        activity: BugActivity,
244        /// What instruction was executing?
245        diagnostics: CurrentInstructionDiagnostics,
246        /// What went wrong?
247        message: String,
248    },
249}
250
251/// Describes an alarm which is active.
252#[allow(clippy::upper_case_acronyms)]
253#[derive(Debug, Clone)]
254pub struct Alarm {
255    pub sequence: Option<SequenceNumber>,
256    pub details: AlarmDetails,
257}
258
259impl Alarm {
260    #[must_use]
261    pub fn kind(&self) -> AlarmKind {
262        self.details.kind()
263    }
264}
265
266impl AlarmDetails {
267    #[must_use]
268    pub fn kind(&self) -> AlarmKind {
269        match self {
270            AlarmDetails::PSAL(_, _) => AlarmKind::PSAL,
271            AlarmDetails::OCSAL(_, _) => AlarmKind::OCSAL,
272            AlarmDetails::QSAL(_, _, _) => AlarmKind::QSAL,
273            AlarmDetails::IOSAL { .. } => AlarmKind::IOSAL,
274            AlarmDetails::MISAL { .. } => AlarmKind::MISAL,
275            AlarmDetails::ROUNDTUITAL { .. } => AlarmKind::ROUNDTUITAL,
276            AlarmDetails::DEFERLOOPAL { .. } => AlarmKind::DEFERLOOPAL,
277            AlarmDetails::BUGAL { .. } => AlarmKind::BUGAL,
278        }
279    }
280}
281
282impl Display for Alarm {
283    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
284        if let Some(seq) = self.sequence {
285            write!(f, "in sequence {seq:o}, {}", self.details)
286        } else {
287            write!(f, "{}", self.details)
288        }
289    }
290}
291
292impl Display for AlarmDetails {
293    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
294        use AlarmDetails::*;
295        match self {
296            QSAL(instruction, op, msg) => {
297                write!(
298                    f,
299                    "QSAL: during execution of instruction {instruction:?}, {op}: {msg}",
300                )
301            }
302            PSAL(address, msg) => {
303                write!(
304                    f,
305                    "PSAL: P register set to illegal address {address:>06o}: {msg}",
306                )
307            }
308            OCSAL(inst, msg) => {
309                write!(
310                    f,
311                    "OCSAL: N register set to invalid instruction {:>012o}: {}",
312                    inst.bits(),
313                    msg
314                )
315            }
316            ROUNDTUITAL {
317                explanation,
318                bug_report_url,
319            } => {
320                write!(
321                    f,
322                    "ROUNDTUITAL: the program used a feature not supported in the emulator: {explanation}. See feature request at {bug_report_url}",
323                )
324            }
325
326            IOSAL {
327                unit,
328                operand,
329                message,
330            } => {
331                write!(f, "IOSAL: I/O alarm during operation on unit {unit:o}")?;
332                if let Some(oper) = operand {
333                    write!(f, " with operand {oper}")?;
334                }
335                write!(f, ": {message}")
336            }
337
338            MISAL { affected_unit } => write!(
339                f,
340                "MISAL: program too slow; missed data for unit {affected_unit:o}"
341            ),
342
343            BUGAL {
344                diagnostics,
345                message,
346                activity,
347            } => {
348                let (issue_type, activity_desc): (Option<IssueType>, &'static str) = match activity
349                {
350                    BugActivity::AlarmHandling => (None, "alarm handling"),
351                    BugActivity::Io => (Some(IssueType::Io), "I/O"),
352                    BugActivity::Opcode => (Some(IssueType::Opcode), "instruction execution"),
353                };
354                let report_url = bug_report_url(message, issue_type);
355                write!(
356                    f,
357                    "BUGAL: encountered a bug in enumator {activity_desc} during execution of {diagnostics}: {message}; please report this as a bug at {report_url}",
358                )
359            }
360            DEFERLOOPAL { address } => {
361                write!(
362                    f,
363                    "DEFERLOOPAL: infinite loop in deferred address at {address:>012o}",
364                )
365            }
366        }
367    }
368}
369
370impl Error for Alarm {}
371
372/// Describes an alarm which is active and is not masked (meaning that
373/// it is actually firing).
374#[derive(Debug)]
375pub struct UnmaskedAlarm {
376    pub alarm: Alarm,
377    pub address: Option<Address>,
378    pub when: Duration,
379}
380
381impl Display for UnmaskedAlarm {
382    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
383        write!(f, "unmasked alarm {}", self.alarm)?;
384        if let Some(address) = self.address {
385            write!(f, "at address {address}")
386        } else {
387            Ok(())
388        }
389    }
390}
391
392/// A trait for objects which implement the firing of alarms.
393pub trait Alarmer {
394    /// Fire the indicated alarm if it is not masked.
395    fn fire_if_not_masked<F: DiagnosticFetcher>(
396        &mut self,
397        alarm_instance: Alarm,
398        get_diags: F,
399    ) -> Result<(), Alarm>;
400    /// Unconditionally fire the indicated alarm.
401    fn always_fire<F: DiagnosticFetcher>(&mut self, alarm_instance: Alarm, get_diags: F) -> Alarm;
402}