cpu/
tx2.rs

1//! Emulation of the TX-2 computer.
2//!
3//! # Principles of Operation
4//!
5//! Calls are non-blocking and update the state of the emulated TX-2
6//! where necessary.
7//!
8//! # Timing
9//!
10//! Method calls return information about how much simulated time they
11//! would have taken up. The caller is responsible for snsuring that
12//! method calls are paced such that the overall execution speed
13//! is whatever it wants (for example 1x speed).
14use std::cmp::min;
15use std::collections::BTreeMap;
16use std::time::Duration;
17
18use tracing::{Level, event, span};
19
20use wasm_bindgen::prelude::*;
21
22use base::prelude::*;
23
24use crate::diagnostics::CurrentInstructionDiagnostics;
25
26use super::PETR;
27use super::alarm::{Alarm, AlarmKind, Alarmer, UnmaskedAlarm};
28use super::alarmunit::AlarmStatus;
29use super::context::Context;
30use super::control::{ConfigurationMemorySetup, ControlUnit, ResetMode, RunMode};
31use super::event::{InputEvent, OutputEvent};
32use super::io::{DeviceManager, ExtendedUnitState, InputFlagRaised, set_up_peripherals};
33use super::memory::{MemoryConfiguration, MemoryUnit};
34use super::{InputEventError, PanicOnUnmaskedAlarm};
35
36/// `Tx2` emulates the TX-2 computer, with peripherals.
37#[wasm_bindgen]
38pub struct Tx2 {
39    control: ControlUnit,
40    mem: MemoryUnit,
41    devices: DeviceManager,
42    next_execution_due: Option<Duration>,
43    next_hw_poll_due: Duration,
44    run_mode: RunMode,
45}
46
47impl Tx2 {
48    /// Create a new instance.
49    pub fn new(
50        ctx: &Context,
51        panic_on_unmasked_alarm: PanicOnUnmaskedAlarm,
52        mem_config: &MemoryConfiguration,
53    ) -> Tx2 {
54        let control = ControlUnit::new(
55            panic_on_unmasked_alarm,
56            ConfigurationMemorySetup::Uninitialised,
57        );
58        event!(
59            Level::DEBUG,
60            "Initial control unit state iis {:?}",
61            &control
62        );
63
64        let mem = MemoryUnit::new(ctx, mem_config);
65        let mut devices = DeviceManager::new();
66        set_up_peripherals(ctx, &mut devices);
67        Tx2 {
68            control,
69            mem,
70            devices,
71            next_execution_due: None,
72            next_hw_poll_due: ctx.simulated_time,
73            run_mode: RunMode::InLimbo,
74        }
75    }
76
77    #[must_use]
78    pub fn get_status_of_alarm(&self, name: &str) -> Option<AlarmStatus> {
79        self.control.get_status_of_alarm(name)
80    }
81
82    #[must_use]
83    pub fn get_alarm_statuses(&self) -> Vec<AlarmStatus> {
84        self.control.get_alarm_statuses()
85    }
86
87    pub fn set_alarm_masked(&mut self, kind: AlarmKind, masked: bool) -> Result<(), Alarm> {
88        self.control.set_alarm_masked(kind, masked)
89    }
90
91    pub fn set_run_mode(&mut self, run_mode: RunMode) {
92        self.run_mode = run_mode;
93    }
94
95    /// Update the emulator's idea of when the next instruction should
96    /// begin execution.
97    pub fn set_next_execution_due(&mut self, now: Duration, newval: Option<Duration>) {
98        if let Some(t) = newval {
99            assert!(now <= t);
100        }
101        event!(
102            Level::TRACE,
103            "Changing next_execution_due from {:?} to {:?}",
104            self.next_execution_due,
105            newval,
106        );
107        self.next_execution_due = newval;
108    }
109
110    /// Update the emulator's idea of when the next hardware state
111    /// change might be.
112    fn set_next_hw_poll_due(&mut self, now: Duration, newval: Duration) {
113        assert!(now <= newval);
114        event!(
115            Level::TRACE,
116            "Changing next_hw_poll_due from {:?} to {:?}",
117            self.next_hw_poll_due,
118            newval,
119        );
120        self.next_hw_poll_due = newval;
121    }
122
123    /// Emulate the user pressing the CODABO key.
124    pub fn codabo(&mut self, ctx: &Context, reset_mode: &ResetMode) -> Result<(), Alarm> {
125        self.control
126            .codabo(ctx, reset_mode, &mut self.devices, &mut self.mem)
127    }
128
129    fn on_input_event(
130        &mut self,
131        ctx: &Context,
132        unit: Unsigned6Bit,
133        event: InputEvent,
134    ) -> Result<InputFlagRaised, InputEventError> {
135        match self.devices.on_input_event(ctx, unit, event) {
136            Ok(InputFlagRaised::Yes) => {
137                // update the poll time for this unit to force it to
138                // be polled
139                self.devices.update_poll_time(ctx, unit);
140                Ok(InputFlagRaised::Yes)
141            }
142            Ok(InputFlagRaised::No) => Ok(InputFlagRaised::No),
143            Err(e) => Err(e),
144        }
145    }
146
147    /// Emulate the effect of the user mounting a paper tape.
148    pub fn mount_paper_tape(
149        &mut self,
150        ctx: &Context,
151        data: Vec<u8>,
152    ) -> Result<InputFlagRaised, InputEventError> {
153        self.on_input_event(ctx, PETR, InputEvent::PetrMountPaperTape { data })
154    }
155
156    /// Emulate the effect of the user pressing a key on one of the
157    /// Lincoln Writers.
158    pub fn lw_input(
159        &mut self,
160        ctx: &Context,
161        unit: Unsigned6Bit,
162        codes: &[Unsigned6Bit],
163    ) -> Result<(bool, InputFlagRaised), String> {
164        let event = InputEvent::LwKeyboardInput {
165            data: codes.to_vec(),
166        };
167        match self.on_input_event(ctx, unit, event) {
168            Ok(flag_raise) => {
169                if flag_raise == InputFlagRaised::Yes {
170                    // We should expect to poll the hardware in the
171                    // next call to tick().
172                    self.next_hw_poll_due = ctx.simulated_time;
173                }
174                Ok((true, flag_raise))
175            }
176            Err(InputEventError::BufferUnavailable) => Ok((false, InputFlagRaised::No)),
177            Err(e) => Err(e.to_string()),
178        }
179    }
180
181    /// Return the time at which the emulator would like to next be
182    /// called.
183    #[must_use]
184    pub fn next_tick(&self) -> Duration {
185        match (
186            self.run_mode,
187            self.next_hw_poll_due,
188            self.next_execution_due,
189        ) {
190            (RunMode::InLimbo, hw, _) | (RunMode::Running, hw, None) => hw,
191            (RunMode::Running, hw, Some(insn)) => min(hw, insn),
192        }
193    }
194
195    fn poll_hw(&mut self, ctx: &Context) -> Result<(), Alarm> {
196        // check for I/O alarms, flag changes.
197        let now = &ctx.simulated_time;
198        event!(Level::TRACE, "polling hardware for updates (now={:?})", now);
199        match self
200            .control
201            .poll_hardware(ctx, &mut self.devices, self.run_mode)
202        {
203            Ok((mode, next)) => {
204                if self.run_mode != mode {
205                    event!(
206                        Level::DEBUG,
207                        "poll_hardware updating run_mode to ({mode:?})"
208                    );
209                }
210                self.run_mode = mode;
211
212                self.set_next_hw_poll_due(
213                    *now,
214                    match next {
215                        Some(when) => when,
216                        None => {
217                            // TODO: check why poll() doesn't always
218                            // return a next-poll time.
219                            *now + Duration::from_micros(5)
220                        }
221                    },
222                );
223                Ok(())
224            }
225            Err(alarm) => {
226                event!(
227                    Level::INFO,
228                    "Alarm raised during hardware polling at system time {:?}",
229                    now
230                );
231                let diags: CurrentInstructionDiagnostics = self.control.diagnostics().clone();
232                self.control.fire_if_not_masked(alarm, diags)
233            }
234        }
235    }
236
237    fn execute_one_instruction(
238        &mut self,
239        ctx: &Context,
240    ) -> Result<(u64, Option<OutputEvent>), UnmaskedAlarm> {
241        let now = &ctx.simulated_time;
242        if self.run_mode == RunMode::InLimbo {
243            event!(
244                Level::WARN,
245                "execute_one_instruction was called while machine is in LIMBO"
246            );
247            self.set_next_execution_due(*now, None);
248            return Ok((0, None));
249        }
250
251        let mut hardware_state_changed: Option<SequenceNumber> = None;
252        match self.control.execute_instruction(
253            ctx,
254            &mut self.devices,
255            &mut self.mem,
256            &mut hardware_state_changed,
257        ) {
258            Err((alarm, address)) => {
259                event!(
260                    Level::INFO,
261                    "Alarm raised during instruction execution at {:o} at system time {:?}",
262                    address,
263                    &ctx.simulated_time
264                );
265                self.set_next_execution_due(*now, None);
266                assert!(self.unmasked_alarm_active());
267                Err(UnmaskedAlarm {
268                    alarm,
269                    address: Some(address),
270                    when: ctx.simulated_time,
271                })
272            }
273            Ok((ns, new_run_mode, maybe_output)) => {
274                match (self.run_mode, new_run_mode) {
275                    (RunMode::Running, RunMode::InLimbo) => {
276                        event!(Level::DEBUG, "Entering LIMBO");
277                        self.set_next_execution_due(*now, None);
278                    }
279                    (RunMode::InLimbo, RunMode::Running) => {
280                        event!(Level::DEBUG, "Leaving LIMBO");
281                        self.set_next_execution_due(*now, Some(*now + Duration::from_nanos(1)));
282                    }
283                    (old_run_mode, new_run_mode) => {
284                        assert_eq!(old_run_mode, new_run_mode);
285                    }
286                }
287                self.run_mode = new_run_mode;
288
289                if let Some(seq) = hardware_state_changed {
290                    // Some instruction changed the hardware, so we need to
291                    // poll it again.
292                    event!(
293                        Level::DEBUG,
294                        "hardware state change for unit {seq}; bringing forward next hardware poll"
295                    );
296                    self.set_next_hw_poll_due(*now, *now + Duration::from_nanos(1));
297                } else {
298                    event!(
299                        Level::TRACE,
300                        "current instruction did not affect the hardware"
301                    );
302                }
303                // TODO: eliminate ns, just change state of `self`.
304                Ok((ns, maybe_output))
305            }
306        }
307    }
308
309    /// Perform whatever emulation action is due now.  If the TX-2
310    /// executes a TSD instruction, return the I/O data being emitted.
311    pub fn tick(&mut self, ctx: &Context) -> Result<Option<OutputEvent>, UnmaskedAlarm> {
312        let system_time = ctx.simulated_time;
313        let tick_span = span!(Level::INFO, "tick", t=?system_time);
314        let _enter = tick_span.enter();
315        event!(
316            Level::TRACE,
317            "tick: system_time={:?}, next_execution_due={:?}, next_hw_poll_due={:?}",
318            system_time,
319            self.next_execution_due,
320            self.next_hw_poll_due
321        );
322        let due: Duration = if let Some(inst_due) = self.next_execution_due {
323            min(self.next_hw_poll_due, inst_due)
324        } else {
325            self.next_hw_poll_due
326        };
327        if due > system_time {
328            let premature_by = due - system_time;
329            event!(
330                Level::WARN,
331                "tick() was called {premature_by:?} prematurely (run mode is {:?})",
332                &self.run_mode
333            );
334        }
335
336        if ctx.simulated_time >= self.next_hw_poll_due {
337            event!(
338                Level::DEBUG,
339                "tick(): polling the hardware (because this is due now)"
340            );
341            let prev_poll_due = self.next_hw_poll_due;
342            match self.poll_hw(ctx) {
343                Ok(()) => {
344                    if self.next_hw_poll_due == prev_poll_due {
345                        event!(
346                            Level::WARN,
347                            "polled hardware successfully at system time {:?}, but poll_hw returned with next_hw_poll_due={:?}",
348                            system_time,
349                            self.next_hw_poll_due
350                        );
351                    }
352                }
353                Err(alarm) => {
354                    return Err(UnmaskedAlarm {
355                        alarm,
356                        address: None, // not executing an instruction
357                        when: ctx.simulated_time,
358                    });
359                }
360            }
361        } else {
362            event!(
363                Level::TRACE,
364                "not polling hardware for updates (remaining wait: {:?})",
365                self.next_hw_poll_due - system_time,
366            );
367        }
368
369        if self.run_mode == RunMode::InLimbo {
370            // No sequence is active, so there is no CPU instruction
371            // to execute.  Therefore we can only leave the limbo
372            // state in response to a hardware event.  We already know
373            // that we need to check for that at `next_hw_poll`.
374            let interval: Duration = self.next_hw_poll_due - system_time;
375            event!(
376                Level::TRACE,
377                "machine is in limbo, waiting {:?} for a flag to be raised",
378                &interval,
379            );
380            // There can be no output event, because no instruction
381            // was executed to generate it.
382            Ok(None)
383        } else if self.unmasked_alarm_active() {
384            event!(
385                Level::DEBUG,
386                "will not execute the next instruction because there is an an unmasked alarm."
387            );
388            Ok(None) // no output event (as we executed no instruction)
389        } else {
390            // Not in limbo, it may be time to execute an instruction.
391            match self.next_execution_due {
392                Some(next) if next <= system_time => {
393                    let (ns, maybe_output) = self.execute_one_instruction(ctx)?;
394                    let mut due = next + Duration::from_nanos(ns);
395                    if due <= system_time {
396                        due = system_time + Duration::from_nanos(1);
397                    }
398                    self.set_next_execution_due(system_time, Some(due));
399                    Ok(maybe_output)
400                }
401                None => {
402                    event!(
403                        Level::TRACE,
404                        "instruction execution clock is not running, no instruction to execute"
405                    );
406                    Ok(None)
407                }
408                Some(next) => {
409                    let wait_for = next - system_time;
410                    event!(
411                        Level::TRACE,
412                        "next instruction execution not due for {wait_for:?}"
413                    );
414                    Ok(None)
415                }
416            }
417        }
418    }
419
420    #[must_use]
421    pub fn unmasked_alarm_active(&self) -> bool {
422        self.control.unmasked_alarm_active()
423    }
424
425    pub fn drain_alarm_changes(&mut self) -> BTreeMap<AlarmKind, AlarmStatus> {
426        self.control.drain_alarm_changes()
427    }
428
429    pub fn disconnect_all_devices(&mut self, ctx: &Context) -> Result<(), Alarm> {
430        self.devices.disconnect_all(ctx, &mut self.control)
431    }
432
433    fn extended_state_of_software_sequence(
434        &self,
435        seq: Unsigned6Bit,
436        index_value: Signed18Bit,
437    ) -> ExtendedUnitState {
438        ExtendedUnitState {
439            flag: self.control.current_flag_state(&seq),
440            connected: false,
441            in_maintenance: false,
442            name: format!("Sequence {seq:>02o}"),
443            status: None,
444            text_info: "(software only)".to_string(),
445            index_value,
446        }
447    }
448
449    fn software_sequence_statuses(&self) -> BTreeMap<Unsigned6Bit, ExtendedUnitState> {
450        let regvalues = self.control.inspect_registers();
451        [u6!(0), u6!(0o76), u6!(0o77)]
452            .into_iter()
453            .map(|seq| {
454                let index_value: &Signed18Bit = regvalues
455                    .index_regs
456                    .get(usize::from(seq))
457                    .expect("software sequences should all have valid index register values");
458                (
459                    seq,
460                    self.extended_state_of_software_sequence(seq, *index_value),
461                )
462            })
463            .collect()
464    }
465
466    pub fn sequence_statuses(
467        &mut self,
468        ctx: &Context,
469    ) -> Result<BTreeMap<Unsigned6Bit, ExtendedUnitState>, Alarm> {
470        // Get the status of the hardware units
471        let mut result: BTreeMap<Unsigned6Bit, ExtendedUnitState> =
472            self.devices.device_statuses(ctx, &mut self.control)?;
473        // Merge in the status of the software units
474        result.append(&mut self.software_sequence_statuses());
475        Ok(result)
476    }
477
478    pub fn drain_device_changes(
479        &mut self,
480        ctx: &Context,
481    ) -> Result<BTreeMap<Unsigned6Bit, ExtendedUnitState>, Alarm> {
482        let mut mapping = self.devices.drain_changes(ctx, &mut self.control)?;
483        for (seq, index_value) in self.control.drain_flag_changes().into_iter() {
484            mapping
485                .entry(seq)
486                .or_insert_with(|| self.extended_state_of_software_sequence(seq, index_value));
487        }
488        Ok(mapping)
489    }
490}