Skip to main content

cpu/
alarm.rs

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