1use crate::{DecodeError, InstructionTiming};
2
3#[derive(Clone, Debug, PartialEq, Eq)]
4pub struct InstructionInfo {
5 pub opcode: u8,
6 pub mnemonic: String,
7 pub size: u8,
8 pub timing: InstructionTiming,
9}
10
11const REGS: [&str; 8] = ["B", "C", "D", "E", "H", "L", "M", "A"];
12const RPS: [&str; 4] = ["B", "D", "H", "SP"];
13const CONDS: [&str; 8] = ["NZ", "Z", "NC", "C", "PO", "PE", "P", "M"];
14
15pub const fn is_undocumented_opcode(opcode: u8) -> bool {
16 matches!(
17 opcode,
18 0x08 | 0x10 | 0x18 | 0x20 | 0x28 | 0x30 | 0x38 | 0xCB | 0xD9 | 0xDD | 0xED | 0xFD
19 )
20}
21
22pub fn decode_opcode(opcode: u8) -> Result<InstructionInfo, DecodeError> {
23 if is_undocumented_opcode(opcode) {
24 return Err(DecodeError::UndocumentedOpcode(opcode));
25 }
26
27 if (0x40..=0x7F).contains(&opcode) {
28 return Ok(if opcode == 0x76 {
29 info(opcode, "HLT", 1, InstructionTiming::fixed(7))
30 } else {
31 let dst = ((opcode >> 3) & 7) as usize;
32 let src = (opcode & 7) as usize;
33 let cycles = if dst == 6 || src == 6 { 7 } else { 5 };
34 info(
35 opcode,
36 format!("MOV {},{}", REGS[dst], REGS[src]),
37 1,
38 InstructionTiming::fixed(cycles),
39 )
40 });
41 }
42
43 if (0x80..=0xBF).contains(&opcode) {
44 let src = (opcode & 7) as usize;
45 let cycles = if src == 6 { 7 } else { 4 };
46 let family = match (opcode >> 3) & 7 {
47 0 => "ADD",
48 1 => "ADC",
49 2 => "SUB",
50 3 => "SBB",
51 4 => "ANA",
52 5 => "XRA",
53 6 => "ORA",
54 _ => "CMP",
55 };
56 return Ok(info(
57 opcode,
58 format!("{} {}", family, REGS[src]),
59 1,
60 InstructionTiming::fixed(cycles),
61 ));
62 }
63
64 if opcode & 0xC7 == 0x04 {
65 let reg = ((opcode >> 3) & 7) as usize;
66 return Ok(info(
67 opcode,
68 format!("INR {}", REGS[reg]),
69 1,
70 InstructionTiming::fixed(if reg == 6 { 10 } else { 5 }),
71 ));
72 }
73 if opcode & 0xC7 == 0x05 {
74 let reg = ((opcode >> 3) & 7) as usize;
75 return Ok(info(
76 opcode,
77 format!("DCR {}", REGS[reg]),
78 1,
79 InstructionTiming::fixed(if reg == 6 { 10 } else { 5 }),
80 ));
81 }
82 if opcode & 0xC7 == 0x06 {
83 let reg = ((opcode >> 3) & 7) as usize;
84 return Ok(info(
85 opcode,
86 format!("MVI {},d8", REGS[reg]),
87 2,
88 InstructionTiming::fixed(if reg == 6 { 10 } else { 7 }),
89 ));
90 }
91 if opcode & 0xCF == 0x01 {
92 let rp = ((opcode >> 4) & 3) as usize;
93 return Ok(info(
94 opcode,
95 format!("LXI {},d16", RPS[rp]),
96 3,
97 InstructionTiming::fixed(10),
98 ));
99 }
100 if opcode & 0xCF == 0x03 {
101 let rp = ((opcode >> 4) & 3) as usize;
102 return Ok(info(
103 opcode,
104 format!("INX {}", RPS[rp]),
105 1,
106 InstructionTiming::fixed(5),
107 ));
108 }
109 if opcode & 0xCF == 0x09 {
110 let rp = ((opcode >> 4) & 3) as usize;
111 return Ok(info(
112 opcode,
113 format!("DAD {}", RPS[rp]),
114 1,
115 InstructionTiming::fixed(10),
116 ));
117 }
118 if opcode & 0xCF == 0x0B {
119 let rp = ((opcode >> 4) & 3) as usize;
120 return Ok(info(
121 opcode,
122 format!("DCX {}", RPS[rp]),
123 1,
124 InstructionTiming::fixed(5),
125 ));
126 }
127 if opcode & 0xC7 == 0xC0 {
128 let cond = ((opcode >> 3) & 7) as usize;
129 return Ok(info(
130 opcode,
131 format!("R{}", CONDS[cond]),
132 1,
133 InstructionTiming::conditional(11, 5),
134 ));
135 }
136 if opcode & 0xC7 == 0xC2 {
137 let cond = ((opcode >> 3) & 7) as usize;
138 return Ok(info(
139 opcode,
140 format!("J{} a16", CONDS[cond]),
141 3,
142 InstructionTiming::conditional(10, 10),
143 ));
144 }
145 if opcode & 0xC7 == 0xC4 {
146 let cond = ((opcode >> 3) & 7) as usize;
147 return Ok(info(
148 opcode,
149 format!("C{} a16", CONDS[cond]),
150 3,
151 InstructionTiming::conditional(17, 11),
152 ));
153 }
154 if opcode & 0xC7 == 0xC7 {
155 let n = (opcode >> 3) & 7;
156 return Ok(info(
157 opcode,
158 format!("RST {}", n),
159 1,
160 InstructionTiming::fixed(11),
161 ));
162 }
163 if opcode & 0xCF == 0xC1 {
164 return Ok(info(
165 opcode,
166 format!("POP {}", stack_pair(opcode)),
167 1,
168 InstructionTiming::fixed(10),
169 ));
170 }
171 if opcode & 0xCF == 0xC5 {
172 return Ok(info(
173 opcode,
174 format!("PUSH {}", stack_pair(opcode)),
175 1,
176 InstructionTiming::fixed(11),
177 ));
178 }
179
180 Ok(match opcode {
181 0x00 => info(opcode, "NOP", 1, InstructionTiming::fixed(4)),
182 0x02 => info(opcode, "STAX B", 1, InstructionTiming::fixed(7)),
183 0x07 => info(opcode, "RLC", 1, InstructionTiming::fixed(4)),
184 0x0A => info(opcode, "LDAX B", 1, InstructionTiming::fixed(7)),
185 0x0F => info(opcode, "RRC", 1, InstructionTiming::fixed(4)),
186 0x12 => info(opcode, "STAX D", 1, InstructionTiming::fixed(7)),
187 0x17 => info(opcode, "RAL", 1, InstructionTiming::fixed(4)),
188 0x1A => info(opcode, "LDAX D", 1, InstructionTiming::fixed(7)),
189 0x1F => info(opcode, "RAR", 1, InstructionTiming::fixed(4)),
190 0x22 => info(opcode, "SHLD a16", 3, InstructionTiming::fixed(16)),
191 0x27 => info(opcode, "DAA", 1, InstructionTiming::fixed(4)),
192 0x2A => info(opcode, "LHLD a16", 3, InstructionTiming::fixed(16)),
193 0x2F => info(opcode, "CMA", 1, InstructionTiming::fixed(4)),
194 0x32 => info(opcode, "STA a16", 3, InstructionTiming::fixed(13)),
195 0x37 => info(opcode, "STC", 1, InstructionTiming::fixed(4)),
196 0x3A => info(opcode, "LDA a16", 3, InstructionTiming::fixed(13)),
197 0x3F => info(opcode, "CMC", 1, InstructionTiming::fixed(4)),
198 0xC3 => info(opcode, "JMP a16", 3, InstructionTiming::fixed(10)),
199 0xC6 => info(opcode, "ADI d8", 2, InstructionTiming::fixed(7)),
200 0xC9 => info(opcode, "RET", 1, InstructionTiming::fixed(10)),
201 0xCD => info(opcode, "CALL a16", 3, InstructionTiming::fixed(17)),
202 0xCE => info(opcode, "ACI d8", 2, InstructionTiming::fixed(7)),
203 0xD3 => info(opcode, "OUT d8", 2, InstructionTiming::fixed(10)),
204 0xD6 => info(opcode, "SUI d8", 2, InstructionTiming::fixed(7)),
205 0xDB => info(opcode, "IN d8", 2, InstructionTiming::fixed(10)),
206 0xDE => info(opcode, "SBI d8", 2, InstructionTiming::fixed(7)),
207 0xE3 => info(opcode, "XTHL", 1, InstructionTiming::fixed(18)),
208 0xE6 => info(opcode, "ANI d8", 2, InstructionTiming::fixed(7)),
209 0xE9 => info(opcode, "PCHL", 1, InstructionTiming::fixed(5)),
210 0xEB => info(opcode, "XCHG", 1, InstructionTiming::fixed(5)),
211 0xEE => info(opcode, "XRI d8", 2, InstructionTiming::fixed(7)),
212 0xF3 => info(opcode, "DI", 1, InstructionTiming::fixed(4)),
213 0xF6 => info(opcode, "ORI d8", 2, InstructionTiming::fixed(7)),
214 0xF9 => info(opcode, "SPHL", 1, InstructionTiming::fixed(5)),
215 0xFB => info(opcode, "EI", 1, InstructionTiming::fixed(4)),
216 0xFE => info(opcode, "CPI d8", 2, InstructionTiming::fixed(7)),
217 _ => unreachable!("documented opcode must be covered: {opcode:#04X}"),
218 })
219}
220
221fn info(
222 opcode: u8,
223 mnemonic: impl Into<String>,
224 size: u8,
225 timing: InstructionTiming,
226) -> InstructionInfo {
227 InstructionInfo {
228 opcode,
229 mnemonic: mnemonic.into(),
230 size,
231 timing,
232 }
233}
234
235fn stack_pair(opcode: u8) -> &'static str {
236 match (opcode >> 4) & 3 {
237 0 => "B",
238 1 => "D",
239 2 => "H",
240 _ => "PSW",
241 }
242}