1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
use self::super::super::isa::instruction::{InstructionJumpCondition, AluOperation};
use self::super::super::isa::GeneralPurposeRegisterBank;
use self::super::DisplayMicroOp;


/// Actual μOps executable by the CPU
///
/// The approach is stack-based (think ComputerCraft or FORTH):
/// bytes are individually pushed and popped onto the μstack (separate from the actual program stack),
/// and there is no other storage.
///
/// Each high-level instruction deconstructs losslessly into up to six μOps,
/// with the exception of reserved instructions, which are converted into 6 NOPs.
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum MicroOp {
    /// Do nothing
    ///
    /// Also to pad out the returned instruction
    Nop,
    /// Halt
    Halt,
    /// Load the top of the stack into INS
    LoadInstruction,

    /// Write the address specified by the top two bytes of the μstack into ADR
    AdrWrite,
    /// Read both bytes of ADR onto the μstack
    AdrRead,

    /// Push a byte from the top of the μstack to the stack
    StackPush,
    /// Pop a byte from the stack to the μstack
    StackPop,

    /// Perform an ALU operation
    Alu(AluOperation),

    /// Read a byte from the port specified at the top of the μstack
    PortIn,
    /// Write to the port specified at the top of the μstack a byte from the next byte on the μstack
    PortOut,

    /// Execute the compare instruction with S at the top and the specified register as the next byte on the μstack
    Compare,

    /// Create an immediate value at the top of the μstack
    MakeImmediate(u8),
    /// Read a 1-byte immediate from memory at PC to the top of the μstack, incrementing PC
    LoadImmediate,

    /// Read the value from memory at ADR
    FetchAddress,
    /// Write to memory at ADR the byte on top of the μstack
    WriteAddress,

    /// Check if the specified jump condition is satisfied by the top of the μstack
    CheckJumpCondition(InstructionJumpCondition),
    /// If the top of the μstack is `1`, set PC to ADR.
    /// If it's `0`, do nothing.
    /// Otherwise, error out.
    Jump,

    /// Read the specified register into the top of the μstack.
    ReadRegister(u8),
    /// Write the top of the μstack to the specified register.
    WriteRegister(u8),
}

impl MicroOp {
    /// Get proxy object implementing `Display` for printing μOps in human-readable format
    ///
    /// # Examples
    ///
    /// ```
    /// # use pir_8_emu::isa::instruction::{InstructionJumpCondition, AluOperation};
    /// # use pir_8_emu::isa::GeneralPurposeRegister;
    /// # use pir_8_emu::micro::MicroOp;
    /// # let registers = GeneralPurposeRegister::defaults();
    /// assert_eq!(MicroOp::WriteRegister(registers[1].address()).display(&registers).to_string(),
    ///            "WriteRegister S");
    ///
    /// assert_eq!(MicroOp::Alu(AluOperation::Or).display(&registers).to_string(),
    ///            "Alu OR");
    ///
    /// assert_eq!(MicroOp::CheckJumpCondition(InstructionJumpCondition::Jmpz).display(&registers).to_string(),
    ///            "CheckJumpCondition JMPZ");
    /// ```
    pub fn display<'r, 's: 'r>(&'s self, registers: &'r GeneralPurposeRegisterBank) -> DisplayMicroOp<'r> {
        DisplayMicroOp {
            op: self,
            registers: registers,
        }
    }
}