cpu/control/
op_io.rs

1use std::ops::BitAnd;
2
3use base::bitselect::{BitPos, BitSelector, bit_select};
4use base::prelude::*;
5
6use tracing::{Level, event};
7
8use super::super::event::OutputEvent;
9use super::super::*;
10use super::alarm::{Alarm, AlarmDetails, Alarmer, BadMemOp, BugActivity};
11use super::alarmunit::AlarmUnit;
12use super::context::Context;
13use super::control::{
14    ControlRegisters, ControlUnit, DeviceManager, OpcodeResult, ProgramCounterChange, TrapCircuit,
15};
16use super::exchanger::exchanged_value_for_load;
17use super::io::{TransferFailed, Unit};
18use super::memory::{MemoryMapped, MemoryOpFailure, MemoryUnit, MetaBitChange};
19
20#[derive(Debug, PartialEq, Eq)]
21enum TransferOutcome {
22    /// Indicates a successful transfer operation.
23    Success {
24        /// Indicates that the memory location's meta bit was set.
25        /// This allows the trap circuit to be triggered if necessary.
26        metabit_was_set: bool,
27
28        /// Describes the output (if any) generated by this
29        /// instruction.
30        output: Option<OutputEvent>,
31    },
32
33    /// When the outcome of the TSD is dismiss and wait, we don't
34    /// trigger the trap circuit (not because we think the TX-2 behaved
35    /// this way, but because it keeps the code simpler and we don't
36    /// know if the oposite behaviour is needed).
37    DismissAndWait,
38}
39
40type OpConversion = fn(Address) -> BadMemOp;
41
42fn bad_write(addr: Address) -> BadMemOp {
43    BadMemOp::Write(addr.into())
44}
45
46impl ControlUnit {
47    /// OPR: Implements the IOS opcode and the AOP opcode.
48    ///
49    /// Bits 2.7 and 2.8 of the N register (i.e. the instruction word)
50    /// distinguish between IOS and AOP (which together are known as
51    /// OPR).  This decoding is described in section 7-11
52    /// ("MISCELLANEOUS OPERATION CODES") in Volume 1 of the Technical
53    /// Manual.  Neither of these insructions are indexable or
54    /// configurable (probably because they don't perform a memory
55    /// access).
56    pub(crate) fn op_opr(
57        &mut self,
58        ctx: &Context,
59        mem: &mut MemoryUnit,
60        devices: &mut DeviceManager,
61    ) -> Result<OpcodeResult, Alarm> {
62        // Perform the decoding described in section 7-11
63        // ("MISCELLANEOUS OPERATION CODES") of Volume 1 of the TX-2
64        // Technical Manual.
65        //
66        // The decoding chart there is
67        //
68        // N2.8 | N2.7 | OPR
69        //   0  |  0   | IOS
70        //   0  |  1   | AOP
71        //   1  |  0   | undefined
72        //   1  |  1   | undefined
73        //
74        // Section 10-2.5.3 ("OPERATION CODE ALARM") of the TX-2
75        // Technical Manual (March 1961) states that if bits 2.6..2.1
76        // of an AOP instruction specify an invalid opcode, OCSAL is
77        // raised.  But that text doesn't cover the case where
78        // N2.8==1, since that isn't the AOP case.
79        let b7: bool = bit_select(
80            self.regs.n.bits(),
81            BitSelector {
82                quarter: Quarter::Q2,
83                bitpos: BitPos::B7,
84            },
85        );
86        if bit_select(
87            self.regs.n.bits(),
88            BitSelector {
89                quarter: Quarter::Q2,
90                bitpos: BitPos::B8,
91            },
92        ) {
93            // This is the "undefined" case from the table above.  The
94            // documentation doesn't explicitly state that OCSAL
95            // should be raised for this case, but it is a reasonably
96            // safe interpretation.  If we find that some piece of
97            // software expects a different result, we can look at
98            // this again.
99            Err(self.invalid_opr_subcode(b7))
100        } else {
101            // This opcode is either IOS (00) or AOP (01).
102            if b7 {
103                // It's AOP.  AOP isn't implemented because we have
104                // not found any information about what its opcodes
105                // are, or what they do.
106                let trailing_digit: char = if b7 { '1' } else { '0' };
107                Err(self.alarm_unit.always_fire(Alarm {
108                    sequence: self.regs.k,
109                    details: AlarmDetails::ROUNDTUITAL {
110                        explanation: format!(
111                            "The AOP instruction (opcode 04 with subcode 0{trailing_digit}) is not yet implemented.",
112                        ),
113                        bug_report_url: "https://github.com/TX-2/TX-2-simulator/issues/148",
114                    },
115                }, &self.regs.diagnostic_only))
116            } else {
117                self.op_ios(ctx, mem, devices)
118            }
119        }
120    }
121
122    fn op_ios(
123        &mut self,
124        ctx: &Context,
125        mem: &mut MemoryUnit,
126        devices: &mut DeviceManager,
127    ) -> Result<OpcodeResult, Alarm> {
128        let j = self.regs.n.index_address();
129        let cf = self.regs.n.configuration();
130
131        if cf & 1 != 0 {
132            // Setting the report bit in the configuration value
133            // causes the device's status word from before any mode
134            // change to be copied into the E register (as stated in
135            // section 4-3.6 of the User Handbook).
136            //
137            // Note that this means that the current sequence and the
138            // sequence for which we are generating a report word are
139            // different (in general).
140            let flag_raised: bool = self.regs.flags.current_flag_state(&j);
141            mem.set_e_register(devices.report(
142                ctx,
143                self.regs.k,
144                j,
145                flag_raised,
146                &mut self.alarm_unit,
147                &self.regs.diagnostic_only,
148            )?);
149        }
150        let mut dismiss_reason: Option<&str> = if cf & 0o20 != 0 {
151            Some("dismiss bit set in config")
152        } else {
153            None
154        };
155
156        let operand = self.regs.n.operand_address_and_defer_bit();
157        let result = match u32::from(operand) {
158            0o20_000 => {
159                devices.disconnect(ctx, &j, &mut self.alarm_unit, &self.regs.diagnostic_only)
160            }
161            0o30_000..=0o37_777 => {
162                let mode: Unsigned12Bit = Unsigned12Bit::try_from(operand & 0o07_777).unwrap();
163                ControlUnit::connect_unit(
164                    ctx,
165                    devices,
166                    &mut self.regs,
167                    &mut self.trap,
168                    j,
169                    mode,
170                    &mut self.alarm_unit,
171                )
172            }
173            0o40_000 => {
174                self.regs.flags.lower(&j);
175                if self.regs.k == Some(j) {
176                    // J is the current sequence, so the flag is
177                    // lowered but don't perform a drop-out (see User
178                    // Handbook page 4-7).
179                    self.regs.current_sequence_is_runnable = true;
180                }
181                Ok(())
182            }
183            0o50_000 => {
184                self.regs.flags.raise(&j);
185                if Some(j) == self.regs.k {
186                    dismiss_reason = None;
187                }
188                Ok(())
189            }
190            0o60_000..=0o60777 => {
191                // Select unit XXX
192                Err(self.alarm_unit.always_fire(Alarm {
193                    sequence: self.regs.k,
194                    details: AlarmDetails::ROUNDTUITAL {
195                        explanation: format!(
196                            "IOS operand {operand:o}: Select Unit command is not yet implemented.",
197                        ),
198                        bug_report_url: "https://github.com/TX-2/TX-2-simulator/issues/139",
199                    },
200                }, &self.regs.diagnostic_only))
201            }
202            _ => {
203                let command: u8 = (u32::from(operand) >> 12) as u8;
204                self.alarm_unit.fire_if_not_masked(Alarm {
205                    sequence: self.regs.k,
206                    details: AlarmDetails::IOSAL {
207                        unit: j,
208                        operand: Some(operand),
209                        message: format!(
210                            "IOS operand {operand:o} has unrecognised leading command digit {command:o}",
211                        ),
212                    },
213                }, &self.regs.diagnostic_only)?;
214                // IOSAL is masked.  Just do nothing.
215                Ok(())
216            }
217        };
218        if let Some(reason) = dismiss_reason {
219            self.dismiss_unless_held(reason);
220        }
221        result.map(|()| OpcodeResult {
222            program_counter_change: None,
223            // poll_order_change doesn't always need to be set, but
224            // false positives cost us only compute efficiency.
225            poll_order_change: Some(j),
226            output: None,
227        })
228    }
229
230    fn connect_unit(
231        ctx: &Context,
232        devices: &mut DeviceManager,
233        regs: &mut ControlRegisters,
234        trap: &mut TrapCircuit,
235        unit: Unsigned6Bit,
236        mode: Unsigned12Bit,
237        alarm_unit: &mut AlarmUnit,
238    ) -> Result<(), Alarm> {
239        let maybe_flag_change: Option<FlagChange> = match u8::from(unit) {
240            0o42 => {
241                trap.connect(ctx, mode);
242                None
243            }
244            _ => devices.connect(ctx, regs.k, &unit, mode, alarm_unit, &regs.diagnostic_only)?,
245        };
246        match maybe_flag_change {
247            Some(FlagChange::Raise(reason)) => {
248                event!(Level::DEBUG, "unit {unit:o} raised its flag: {reason}");
249                regs.flags.raise(&unit);
250            }
251            None => (),
252        }
253        Ok(())
254    }
255
256    pub(crate) fn op_tsd(
257        &mut self,
258        ctx: &Context,
259        devices: &mut DeviceManager,
260        execution_address: Address,
261        mem: &mut MemoryUnit,
262    ) -> Result<OpcodeResult, Alarm> {
263        fn make_tsd_qsal(seq: Option<SequenceNumber>, inst: Instruction, op: BadMemOp) -> Alarm {
264            Alarm {
265                sequence: seq,
266                details: AlarmDetails::QSAL(inst, op, "TSD address is not mapped".to_string()),
267            }
268        }
269
270        let result: Result<TransferOutcome, Alarm> = if let Some(unit) = self.regs.k {
271            let target: Address = self.operand_address_with_optional_defer_and_index(ctx, mem)?;
272            let not_mapped = |op_conv: OpConversion| -> Alarm {
273                let op: BadMemOp = op_conv(target);
274                make_tsd_qsal(self.regs.k, self.regs.n, op)
275            };
276
277            let meta_op: MetaBitChange = if self.trap.set_metabits_of_operands() {
278                MetaBitChange::Set
279            } else {
280                MetaBitChange::None
281            };
282            // There are no sequence numbers below 0o40, besides 0.
283            if matches!(u8::from(unit), 0 | 0o75 | 0o76) {
284                // Non-INOUT sequences just cycle the target location;
285                // see section 4-1 (page 4-3) of the Users Handbook;
286                // also pages 4-2 and 4-9).
287                match mem.cycle_full_word_for_tsd(ctx, &target) {
288                    Ok(extra_bits) => Ok(TransferOutcome::Success {
289                        metabit_was_set: extra_bits.meta,
290                        output: None,
291                    }),
292                    Err(MemoryOpFailure::ReadOnly(_address, extra_bits)) => {
293                        // The read-only case is not an error, it's
294                        // normal.  The TSD instruction simply has no
295                        // effect when the target address is
296                        // read-only.
297                        // TODO: should there be an effect on the E register?
298                        Ok(TransferOutcome::Success {
299                            metabit_was_set: extra_bits.meta,
300                            output: None, // not an INOUT unit anyway
301                        })
302                    }
303                    Err(MemoryOpFailure::NotMapped(_)) => {
304                        self.alarm_unit.fire_if_not_masked(
305                            not_mapped(bad_write),
306                            &self.regs.diagnostic_only,
307                        )?;
308                        // QSAL is masked, carry on.
309                        Ok(TransferOutcome::Success {
310                            metabit_was_set: false, // act as if metabit unset
311                            output: None,
312                        })
313                    }
314                }
315            } else {
316                devices.mark_device_changed(unit);
317                match devices.get_mut(&unit) {
318                    None => {
319                        event!(Level::WARN, "TSD on unknown unit {:o}", unit);
320                        Ok(TransferOutcome::DismissAndWait)
321                    }
322                    Some(device) => {
323                        let is_input_unit = device.is_input_unit;
324                        if !device.connected {
325                            // If the unit is not connected, perform
326                            // dismiss and wait.  This requirement is
327                            // described in section 4-3.7 of the Users
328                            // Handbook.
329                            Ok(TransferOutcome::DismissAndWait)
330                        } else if device.in_maintenance {
331                            event!(
332                                Level::WARN,
333                                "TSD on unit {:o}, but it is in maintenance",
334                                unit
335                            );
336                            Ok(TransferOutcome::DismissAndWait)
337                        } else {
338                            // We're actually going to do the (input or output) transfer.
339                            // First load into the M register the existing contents of
340                            // memory.
341                            let transfer_mode = device
342                                .transfer_mode(&mut self.alarm_unit, &self.regs.diagnostic_only)?;
343                            let (m_register, extra_bits) = self
344                                .fetch_operand_from_address_without_exchange(
345                                    ctx,
346                                    mem,
347                                    &target,
348                                    &UpdateE::No,
349                                )?;
350                            if is_input_unit {
351                                // In read operations, data is transferred
352                                // from the I/O device's buffer over the
353                                // IOBM bus, into the E register.  See
354                                // figure 15-18 in Volume 2 of the TX-2
355                                // Techical Manual.
356                                match device.read(
357                                    ctx,
358                                    &mut self.alarm_unit,
359                                    &self.regs.diagnostic_only,
360                                ) {
361                                    Ok(masked_word) => {
362                                        const UPDATE_E_YES: UpdateE = UpdateE::Yes;
363                                        let newval: Unsigned36Bit =
364                                            masked_word.apply(Unsigned36Bit::ZERO);
365                                        match transfer_mode {
366                                            TransferMode::Assembly => {
367                                                let bits: Unsigned6Bit =
368                                                    newval.bitand(Unsigned6Bit::MAX);
369                                                self.memory_store_without_exchange(
370                                                    ctx,
371                                                    mem,
372                                                    &target,
373                                                    &cycle_and_splay(m_register, bits),
374                                                    &UPDATE_E_YES,
375                                                    &meta_op,
376                                                )?;
377                                            }
378                                            TransferMode::Exchange => {
379                                                self.memory_store_with_exchange(
380                                                    ctx,
381                                                    mem,
382                                                    &target,
383                                                    &newval,
384                                                    &m_register,
385                                                    &UPDATE_E_YES,
386                                                    &meta_op,
387                                                )?;
388                                            }
389                                        }
390                                        Ok(TransferOutcome::Success {
391                                            metabit_was_set: extra_bits.meta,
392                                            output: None, // because this is a read unit.
393                                        })
394                                    }
395                                    Err(TransferFailed::BufferNotFree) => {
396                                        Ok(TransferOutcome::DismissAndWait)
397                                    }
398                                    Err(TransferFailed::Alarm(alarm)) => {
399                                        return Err(alarm);
400                                    }
401                                }
402                            } else {
403                                // In write operations, data is
404                                // transferred from the E register to
405                                // the I/O device over the E bus.  See
406                                // figure 15-17 in Volume 2 of the
407                                // TX-2 Techical Manual.
408                                mem.set_e_register(exchanged_value_for_load(
409                                    &self.get_config(),
410                                    &m_register,
411                                    &mem.get_e_register(),
412                                ));
413                                match transfer_mode {
414                                    TransferMode::Exchange => {
415                                        match device.write(ctx, mem.get_e_register(), &self.regs.diagnostic_only) {
416                                            Err(TransferFailed::BufferNotFree) => {
417                                                Ok(TransferOutcome::DismissAndWait)
418                                            }
419                                            Err(TransferFailed::Alarm(alarm)) => Err(alarm),
420                                            Ok(maybe_output) => Ok(TransferOutcome::Success {
421                                                metabit_was_set: extra_bits.meta,
422                                                output: maybe_output,
423                                            }),
424                                        }
425                                    }
426                                    TransferMode::Assembly => Err(self
427                                        .alarm_unit
428                                        .always_fire(Alarm {
429                                        sequence: self.regs.k,
430                                            details: AlarmDetails::ROUNDTUITAL{
431                                                explanation:
432                                                "TSD output in assembly mode is not yet implemented."
433                                                    .to_string(),
434                                                bug_report_url: "https://github.com/TX-2/TX-2-simulator/issues/140",
435                                            },
436                                    }, &self.regs.diagnostic_only)),
437                                }
438                            }
439                        }
440                    }
441                }
442            }
443        } else {
444            Err(self.alarm_unit.always_fire(Alarm {
445                sequence: self.regs.k,
446                details: AlarmDetails::BUGAL {
447                    activity: BugActivity::Opcode,
448                    diagnostics: self.regs.diagnostic_only.clone(),
449                    message: "Executed TSD instruction while the K register is None (i.e. there is no current sequence)".to_string(),
450                }}, &self.regs.diagnostic_only))
451        };
452        match result {
453            Ok(TransferOutcome::Success {
454                metabit_was_set,
455                output,
456            }) => {
457                if metabit_was_set && self.trap.trap_on_operand() {
458                    self.raise_trap();
459                }
460                Ok(OpcodeResult {
461                    program_counter_change: None,
462                    poll_order_change: self.regs.k,
463                    output,
464                })
465            }
466            Ok(TransferOutcome::DismissAndWait) => {
467                // In the dismiss and wait case, the
468                // sequence is dismissed even if the hold
469                // bit is set (Users Handbook, section
470                // 4-3.2).  The hold bit only governs what
471                // happens following the completion of an
472                // instruction.
473                self.dismiss("TSD while data was not ready caused dismiss-and-wait");
474                Ok(OpcodeResult {
475                    program_counter_change: Some(ProgramCounterChange::DismissAndWait(
476                        execution_address,
477                    )),
478                    poll_order_change: self.regs.k,
479                    output: None,
480                })
481            }
482            Err(e) => Err(e),
483        }
484    }
485}