decasm/
lib.rs

1use std::fmt::Debug;
2
3use derive_more::UpperHex;
4use strum_macros::{Display, EnumDiscriminants, EnumIter};
5use thiserror::Error;
6use ux::{u12, u4};
7
8/// One of the 16 CHIP-8 variable registers `V0`–`VF`.
9#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
10pub struct Register(pub u4);
11
12impl From<Register> for usize {
13    fn from(register: Register) -> Self {
14        usize::try_from(register.0).unwrap()
15    }
16}
17
18/// The possible byte operands in a CHIP-8 opcode: A nibble representing a [`Register`] holding a byte, or an immediate byte value.
19#[derive(Copy, Clone, Debug, PartialEq, Eq)]
20pub enum Byte {
21    Register(Register),
22    Immediate(u8),
23}
24
25#[derive(UpperHex)]
26#[upper_hex(fmt = "UpperHex")]
27enum Address {
28    Address(u12),
29    LongAddress(u16),
30}
31
32// TODO
33impl Default for Byte {
34    fn default() -> Self {
35        //Self::Register(Register::default())
36        Self::Immediate(u8::default())
37    }
38}
39
40impl From<Byte> for u8 {
41    fn from(byte: Byte) -> Self {
42        match byte {
43            Byte::Register(Register(x)) => u8::from(x),
44            Byte::Immediate(x) => x,
45        }
46    }
47}
48
49/// CHIP-8 instructions.
50///
51/// This instruction set is mostly based on what Octo supports, which comprises the specifications for CHIP-8, SUPER-CHIP and XO-CHIP.
52///
53/// However, there is also support for some more esoteric instructions, especially where there are no collisions in the opcode space.
54#[non_exhaustive]
55#[derive(EnumIter, EnumDiscriminants, Copy, Clone, Debug, Display, PartialEq, Eq)]
56#[strum_discriminants(derive(Display))]
57pub enum Instruction {
58    /// Halt the CHIP-8 interpreter.
59    ///
60    /// Based on Octo's behavior when encountering `0000`.
61    Halt,
62    /// Exit the CHIP-8 interpreter with an optional exit code.
63    ///
64    /// Based on the following opcodes:
65    /// * `00FD`: Exit interpreter (from SUPER-CHIP 1.1)
66    /// * `001N`: Exit with exit code `N` (from [`chip8run`](http://chip8.sourceforge.net/))
67    Exit(Option<u8>),
68    /// Scroll the display up.
69    ///
70    /// Based on the following opcodes:
71    /// * `00BN`: Scroll up by N pixels (from [Massung's SUPER-CHIP interpreter](https://chip-8.github.io/extensions/#super-chip-with-scroll-up) and Mega-Chip)
72    /// * `00DN`: Scroll up by N pixels (from [XO-CHIP](http://johnearnest.github.io/Octo/docs/XO-ChipSpecification.html))
73    ScrollUp(u4),
74    /// Scroll the display down.
75    ///
76    /// Based on the following opcode:
77    /// * `00CN`: Scroll down by N pixels
78    ScrollDown(u4),
79    /// Scroll the display right.
80    ScrollRight,
81    /// Scroll the display left.
82    ScrollLeft,
83    /// Clear the display.
84    Clear,
85    /// Return from subroutine.
86    Return,
87    /// Toggle the behavior of `Instruction::Load` and `Instruction::Store`
88    ToggleLoadStoreQuirk,
89    /// Change display to low resolution ("lores") mode, 64x32 pixels
90    LoRes,
91    /// Change display to high resolution ("hires") mode, 128x64 pixels
92    HiRes,
93    /// Call machine code routine.
94    CallMachineCode(u12),
95    /// Jump to memory address
96    Jump(u12),
97    /// Call subroutine
98    Call(u12),
99    /// Skip the next instruction if
100    SkipIfEqual(Register, Byte),
101    SkipIfNotEqual(Register, Byte),
102    Add(Register, Byte),
103    Set(Register, Byte),
104    Or(Register, Register),
105    And(Register, Register),
106    Xor(Register, Register),
107    Sub(Register, Register),
108    ShiftRight(Register, Register),
109    ShiftLeft(Register, Register),
110    SubReverse(Register, Register),
111    SetIndex(u12),
112    SetIndexLong(u16),
113    JumpRelative(u12),
114    Random(Register, u8),
115    /// Draw a sprite on the display.
116    Draw(Register, Register, u4),
117    SkipKey(Register),
118    SkipNotKey(Register),
119    LoadAudio,
120    LoadDelay(Register),
121    BlockKey(Register),
122    SelectPlane(u4),
123    SetPitch(Register),
124    SetDelay(Register),
125    SetSound(Register),
126    AddRegisterToIndex(Register),
127    FontCharacter(Register),
128    BigFontCharacter(Register),
129    Bcd(Register),
130    Store(Register),
131    Load(Register),
132    StoreRange(Register, Register),
133    LoadRange(Register, Register),
134    StoreFlags(Register),
135    LoadFlags(Register),
136}
137
138#[non_exhaustive]
139#[derive(EnumIter, Copy, Clone, Debug, Display, UpperHex, PartialEq, Eq)]
140#[upper_hex(fmt = "UpperHex")]
141pub enum Opcode {
142    Opcode(u16),
143    LongOpcode(u32),
144}
145
146impl From<Instruction> for Opcode {
147    fn from(instruction: Instruction) -> Opcode {
148        match instruction {
149            Instruction::Halt => Opcode::Opcode(0x0000),
150            Instruction::Exit(None) => Opcode::Opcode(0x00FD),
151            Instruction::Exit(Some(n)) => Opcode::Opcode(0x0010 | u16::from(n)),
152            Instruction::ScrollDown(n) => Opcode::Opcode(0x00C0 | u16::from(n)),
153            Instruction::ScrollUp(n) => Opcode::Opcode(0x00D0 | u16::from(n)),
154            Instruction::ToggleLoadStoreQuirk => Opcode::Opcode(0x00FA),
155            Instruction::ScrollRight => Opcode::Opcode(0x00FB),
156            Instruction::ScrollLeft => Opcode::Opcode(0x00FC),
157            Instruction::LoRes => Opcode::Opcode(0x00FE),
158            Instruction::HiRes => Opcode::Opcode(0x00FF),
159            Instruction::Clear => Opcode::Opcode(0x00E0),
160            Instruction::Return => Opcode::Opcode(0x00EE),
161            Instruction::CallMachineCode(nnn) => Opcode::Opcode(u16::from(nnn)),
162            Instruction::Jump(nnn) => Opcode::Opcode(0x1000 | u16::from(nnn)),
163            Instruction::Call(nnn) => Opcode::Opcode(0x2000 | u16::from(nnn)),
164            Instruction::SkipIfEqual(Register(x), Byte::Immediate(kk)) => {
165                Opcode::Opcode(0x3000 | (u16::from(x) << 8) | u16::from(kk))
166            }
167            Instruction::SkipIfEqual(Register(x), Byte::Register(Register(y))) => {
168                Opcode::Opcode(0x5000 | (u16::from(x) << 8) | (u16::from(y) << 4))
169            }
170            Instruction::SkipIfNotEqual(Register(x), Byte::Immediate(kk)) => {
171                Opcode::Opcode(0x4000 | (u16::from(x) << 8) | u16::from(kk))
172            }
173            Instruction::SkipIfNotEqual(Register(x), Byte::Register(Register(y))) => {
174                Opcode::Opcode(0x9000 | (u16::from(x) << 8) | (u16::from(y) << 4))
175            }
176            Instruction::Set(Register(x), Byte::Immediate(kk)) => {
177                Opcode::Opcode(0x6000 | (u16::from(x) << 8) | u16::from(kk))
178            }
179            Instruction::Add(Register(x), Byte::Immediate(kk)) => {
180                Opcode::Opcode(0x7000 | (u16::from(x) << 8) | u16::from(kk))
181            }
182            Instruction::Set(Register(x), Byte::Register(Register(y))) => {
183                Opcode::Opcode(0x8000 | (u16::from(x) << 8) | (u16::from(y) << 4))
184            }
185            Instruction::Add(Register(x), Byte::Register(Register(y))) => {
186                Opcode::Opcode(0x8004 | (u16::from(x) << 8) | (u16::from(y) << 4))
187            }
188            Instruction::Or(Register(x), Register(y)) => {
189                Opcode::Opcode(0x8001 | (u16::from(x) << 8) | (u16::from(y) << 4))
190            }
191            Instruction::And(Register(x), Register(y)) => {
192                Opcode::Opcode(0x8002 | (u16::from(x) << 8) | (u16::from(y) << 4))
193            }
194            Instruction::Xor(Register(x), Register(y)) => {
195                Opcode::Opcode(0x8003 | (u16::from(x) << 8) | (u16::from(y) << 4))
196            }
197            Instruction::Sub(Register(x), Register(y)) => {
198                Opcode::Opcode(0x8005 | (u16::from(x) << 8) | (u16::from(y) << 4))
199            }
200            Instruction::ShiftRight(Register(x), Register(y)) => {
201                Opcode::Opcode(0x8006 | (u16::from(x) << 8) | (u16::from(y) << 4))
202            }
203            Instruction::ShiftLeft(Register(x), Register(y)) => {
204                Opcode::Opcode(0x800E | (u16::from(x) << 8) | (u16::from(y) << 4))
205            }
206            Instruction::SubReverse(Register(x), Register(y)) => {
207                Opcode::Opcode(0x8007 | (u16::from(x) << 8) | (u16::from(y) << 4))
208            }
209            Instruction::SetIndex(nnn) => Opcode::Opcode(0xA000 | u16::from(nnn)),
210            Instruction::SetIndexLong(nnnn) => Opcode::LongOpcode(0xF000_0000 | u32::from(nnnn)),
211            Instruction::JumpRelative(nnn) => Opcode::Opcode(0xB000 | u16::from(nnn)),
212            Instruction::Random(Register(x), kk) => {
213                Opcode::Opcode(0xC000 | (u16::from(x) << 8) | u16::from(kk))
214            }
215            Instruction::Draw(Register(x), Register(y), n) => {
216                Opcode::Opcode(0xD000 | (u16::from(x) << 8) | (u16::from(y) << 4) | u16::from(n))
217            }
218            Instruction::SkipKey(Register(x)) => Opcode::Opcode(0xE09E | (u16::from(x) << 8)),
219            Instruction::SkipNotKey(Register(x)) => Opcode::Opcode(0xE0A1 | (u16::from(x) << 8)),
220            Instruction::LoadAudio => Opcode::Opcode(0xF002),
221            Instruction::LoadDelay(Register(x)) => Opcode::Opcode(0xF007 | (u16::from(x) << 8)),
222            Instruction::BlockKey(Register(x)) => Opcode::Opcode(0xF00A | (u16::from(x) << 8)),
223            Instruction::SelectPlane(n) => Opcode::Opcode(0xF001 | (u16::from(n) << 8)),
224            Instruction::SetPitch(Register(x)) => Opcode::Opcode(0xF03A | (u16::from(x) << 8)),
225            Instruction::SetDelay(Register(x)) => Opcode::Opcode(0xF015 | (u16::from(x) << 8)),
226            Instruction::SetSound(Register(x)) => Opcode::Opcode(0xF018 | (u16::from(x) << 8)),
227            Instruction::AddRegisterToIndex(Register(x)) => {
228                Opcode::Opcode(0xF01E | (u16::from(x) << 8))
229            }
230            Instruction::FontCharacter(Register(x)) => Opcode::Opcode(0xF029 | (u16::from(x) << 8)),
231            Instruction::BigFontCharacter(Register(x)) => {
232                Opcode::Opcode(0xF030 | (u16::from(x) << 8))
233            }
234            Instruction::Bcd(Register(x)) => Opcode::Opcode(0xF033 | (u16::from(x) << 8)),
235            Instruction::Store(Register(x)) => Opcode::Opcode(0xF055 | (u16::from(x) << 8)),
236            Instruction::Load(Register(x)) => Opcode::Opcode(0xF065 | (u16::from(x) << 8)),
237            Instruction::StoreFlags(Register(x)) => Opcode::Opcode(0xF075 | (u16::from(x) << 8)),
238            Instruction::LoadFlags(Register(x)) => Opcode::Opcode(0xF085 | (u16::from(x) << 8)),
239            Instruction::StoreRange(Register(x), Register(y)) => {
240                Opcode::Opcode(0x5002 | (u16::from(x) << 8) | (u16::from(y) << 4))
241            }
242            Instruction::LoadRange(Register(x), Register(y)) => {
243                Opcode::Opcode(0x5003 | (u16::from(x) << 8) | (u16::from(y) << 4))
244            }
245        }
246    }
247}
248
249#[derive(Error)]
250pub enum DecodeError {
251    #[error("unknown opcode: {0}")]
252    UnknownOpcodeError(Opcode),
253    #[error("incomplete opcode: {0}")]
254    IncompleteLongOpcodeError(InstructionDiscriminants, Box<dyn Fn(u16) -> Instruction>),
255}
256
257impl Debug for DecodeError {
258    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259        match self {
260            Self::UnknownOpcodeError(arg0) => {
261                f.debug_tuple("UnknownOpcodeError").field(arg0).finish()
262            }
263            Self::IncompleteLongOpcodeError(arg0, arg1) => f
264                .debug_tuple("IncompleteLongOpcodeError")
265                .field(arg0)
266                .finish(),
267        }
268    }
269}
270
271impl TryFrom<Opcode> for Instruction {
272    type Error = DecodeError;
273
274    fn try_from(op: Opcode) -> Result<Self, Self::Error> {
275        match op {
276            Opcode::LongOpcode(opcode) => {
277                let prefix = u16::try_from(opcode >> 16).unwrap();
278                let suffix = u16::try_from(opcode & 0x0000_FFFF).unwrap();
279                if prefix == 0xF000 {
280                    Ok(Instruction::SetIndexLong(suffix))
281                } else {
282                    Err(DecodeError::UnknownOpcodeError(op))
283                    //Err(format!("Unknown long opcode {:#010x}", opcode))
284                }
285            }
286            Opcode::Opcode(opcode) => {
287                let x = u4::try_from((opcode & 0x0F00) >> 8).unwrap();
288                let y = u4::try_from((opcode & 0x00F0) >> 4).unwrap();
289                let nnn = u12::try_from(opcode & 0x0FFF).unwrap();
290                let kk = (opcode & 0x00FF) as u8;
291                let n = u4::try_from(opcode & 0x000F).unwrap();
292
293                let op1 = (opcode & 0xF000) >> 12;
294                let op2 = (opcode & 0x0F00) >> 8;
295                let op3 = (opcode & 0x00F0) >> 4;
296                let op4 = opcode & 0x000F;
297
298                Ok(
299                    match (op1, op2, op3, op4) {
300                        #![allow(clippy::match_same_arms)]
301                        (0x0, 0x0, 0x0, 0x0) => Instruction::Halt,
302                        (0x0, 0x0, 0x1, _n) => Instruction::Exit(Some(op4 as u8)),
303                        (0x0, 0x0, 0xB, _n) => Instruction::ScrollUp(n),
304                        (0x0, 0x0, 0xC, _n) => Instruction::ScrollDown(n),
305                        (0x0, 0x0, 0xD, _n) => Instruction::ScrollUp(n),
306                        (0x0, 0x0, 0xE, 0x0) => Instruction::Clear,
307                        (0x0, 0x0, 0xE, 0xE) => Instruction::Return,
308                        (0x0, 0x0, 0xF, 0xA) => Instruction::ToggleLoadStoreQuirk,
309                        (0x0, 0x0, 0xF, 0xB) => Instruction::ScrollRight,
310                        (0x0, 0x0, 0xF, 0xC) => Instruction::ScrollLeft,
311                        (0x0, 0x0, 0xF, 0xD) => Instruction::Exit(None),
312                        (0x0, 0x0, 0xF, 0xE) => Instruction::LoRes,
313                        (0x0, 0x0, 0xF, 0xF) => Instruction::HiRes,
314                        (0x0, _, _, _) => Instruction::CallMachineCode(nnn),
315                        (0x1, _, _, _) => Instruction::Jump(nnn),
316                        (0x2, _, _, _) => Instruction::Call(nnn),
317                        (0x3, _x, _, _) => {
318                            Instruction::SkipIfEqual(Register(x), Byte::Immediate(kk))
319                        }
320                        (0x4, _x, _, _) => {
321                            Instruction::SkipIfNotEqual(Register(x), Byte::Immediate(kk))
322                        }
323                        (0x5, _x, _y, 0x0) => {
324                            Instruction::SkipIfEqual(Register(x), Byte::Register(Register(y)))
325                        }
326                        (0x5, _x, _y, 0x2) => Instruction::StoreRange(Register(x), Register(y)),
327                        (0x5, _x, _y, 0x3) => Instruction::LoadRange(Register(x), Register(y)),
328                        (0x6, _x, _, _) => Instruction::Set(Register(x), Byte::Immediate(kk)),
329                        (0x7, _x, _, _) => Instruction::Add(Register(x), Byte::Immediate(kk)),
330                        (0x8, _x, _y, 0) => {
331                            Instruction::Set(Register(x), Byte::Register(Register(y)))
332                        }
333                        (0x8, _x, _y, 1) => Instruction::Or(Register(x), Register(y)),
334                        (0x8, _x, _y, 2) => Instruction::And(Register(x), Register(y)),
335                        (0x8, _x, _y, 3) => Instruction::Xor(Register(x), Register(y)),
336                        (0x8, _x, _y, 4) => {
337                            Instruction::Add(Register(x), Byte::Register(Register(y)))
338                        }
339                        (0x8, _x, _y, 5) => Instruction::Sub(Register(x), Register(y)),
340                        (0x8, _x, _y, 0x6) => Instruction::ShiftRight(Register(x), Register(y)),
341                        (0x8, _x, _y, 0x7) => Instruction::SubReverse(Register(x), Register(y)),
342                        (0x8, _x, _y, 0xE) => Instruction::ShiftLeft(Register(x), Register(y)),
343                        (0x9, _x, _y, 0x0) => {
344                            Instruction::SkipIfNotEqual(Register(x), Byte::Register(Register(y)))
345                        }
346                        (0xA, _, _, _) => Instruction::SetIndex(nnn),
347                        (0xB, _, _, _) => Instruction::JumpRelative(nnn),
348                        (0xC, _x, _, _) => Instruction::Random(Register(x), kk),
349                        (0xD, _x, _y, _n) => Instruction::Draw(Register(x), Register(y), n),
350                        (0xE, _x, 0x9, 0xE) => Instruction::SkipKey(Register(x)),
351                        (0xE, _x, 0xA, 0x1) => Instruction::SkipNotKey(Register(x)),
352                        (0xF, 0x0, 0x0, 0x0) => {
353                            return Err(DecodeError::IncompleteLongOpcodeError(
354                                InstructionDiscriminants::SetIndexLong,
355                                Box::new(Instruction::SetIndexLong),
356                            ));
357                            //return Err(format!("Incomplete long opcode {:#06x}", opcode))
358                        }
359                        (0xF, 0x0, 0x0, 0x2) => Instruction::LoadAudio,
360                        (0xF, _x, 0x0, 0x7) => Instruction::LoadDelay(Register(x)),
361                        (0xF, _x, 0x0, 0xA) => Instruction::BlockKey(Register(x)),
362                        (0xF, _n, 0x0, 0x1) => Instruction::SelectPlane(x),
363                        (0xF, _x, 0x1, 0x5) => Instruction::SetDelay(Register(x)),
364                        (0xF, _x, 0x1, 0x8) => Instruction::SetSound(Register(x)),
365                        (0xF, _x, 0x1, 0xE) => Instruction::AddRegisterToIndex(Register(x)),
366                        (0xF, _x, 0x2, 0x9) => Instruction::FontCharacter(Register(x)),
367                        (0xF, _x, 0x3, 0x0) => Instruction::BigFontCharacter(Register(x)),
368                        (0xF, _x, 0x3, 0x3) => Instruction::Bcd(Register(x)),
369                        (0xF, _x, 0x3, 0xA) => Instruction::SetPitch(Register(x)),
370                        (0xF, _x, 0x5, 0x5) => Instruction::Store(Register(x)),
371                        (0xF, _x, 0x6, 0x5) => Instruction::Load(Register(x)),
372                        (0xF, _x, 0x7, 0x5) => Instruction::StoreFlags(Register(x)),
373                        (0xF, _x, 0x8, 0x5) => Instruction::LoadFlags(Register(x)),
374                        _ => return Err(DecodeError::UnknownOpcodeError(op)), // Err(format!("Unknown opcode {:#06x}", opcode)),
375                    },
376                )
377            }
378        }
379    }
380}