| 1 | use crate::isa::{self, Operand}; |
| 2 | use crate::sfr::{format_bit, format_direct, hex8, hex16}; |
| 3 | |
| 4 | pub struct Disasm { |
| 5 | pub text: String, |
| 6 | pub size: u8, |
| 7 | } |
| 8 | |
| 9 | /// Disassembles the instruction at `addr`. |
| 10 | pub fn disassemble(code: &[u8; 0x10000], addr: u16) -> Disasm { |
| 11 | let byte = |i: u16| code[addr.wrapping_add(i) as usize]; |
| 12 | let op = byte(0); |
| 13 | let Some(info) = isa::info(op) else { |
| 14 | return Disasm { |
| 15 | text: format!("DB {}", hex8(op)), |
| 16 | size: 1, |
| 17 | }; |
| 18 | }; |
| 19 | let next = addr.wrapping_add(info.size as u16); |
| 20 | let mut pos = 1u16; |
| 21 | let mut parts: Vec<String> = Vec::new(); |
| 22 | for &o in info.operands { |
| 23 | let s = match o { |
| 24 | Operand::A => "A".into(), |
| 25 | Operand::AB => "AB".into(), |
| 26 | Operand::C => "C".into(), |
| 27 | Operand::Dptr => "DPTR".into(), |
| 28 | Operand::Reg(n) => format!("R{n}"), |
| 29 | Operand::AtReg(n) => format!("@R{n}"), |
| 30 | Operand::AtDptr => "@DPTR".into(), |
| 31 | Operand::AtAPlusDptr => "@A+DPTR".into(), |
| 32 | Operand::AtAPlusPc => "@A+PC".into(), |
| 33 | Operand::Imm8 => format!("#{}", hex8(byte(pos))), |
| 34 | Operand::Imm16 => format!("#{}", hex16(u16::from_be_bytes([byte(pos), byte(pos + 1)]))), |
| 35 | Operand::Direct => format_direct(byte(pos)), |
| 36 | Operand::Bit => format_bit(byte(pos)), |
| 37 | Operand::NotBit => format!("/{}", format_bit(byte(pos))), |
| 38 | Operand::Addr11 => { |
| 39 | hex16((next & 0xF800) | (((op >> 5) as u16) << 8) | byte(pos) as u16) |
| 40 | } |
| 41 | Operand::Addr16 => hex16(u16::from_be_bytes([byte(pos), byte(pos + 1)])), |
| 42 | Operand::Rel => hex16(next.wrapping_add(byte(pos) as i8 as u16)), |
| 43 | }; |
| 44 | pos += o.len() as u16; |
| 45 | parts.push(s); |
| 46 | } |
| 47 | if op == 0x85 { |
| 48 | // Encoded as source, destination. |
| 49 | parts.swap(0, 1); |
| 50 | } |
| 51 | let text = if parts.is_empty() { |
| 52 | info.mnemonic.to_string() |
| 53 | } else { |
| 54 | format!("{:<5} {}", info.mnemonic, parts.join(",")) |
| 55 | }; |
| 56 | Disasm { |
| 57 | text, |
| 58 | size: info.size, |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | #[cfg(test)] |
| 63 | mod tests { |
| 64 | use super::*; |
| 65 | use crate::asm::assemble; |
| 66 | |
| 67 | /// Disassembling and re-assembling every opcode gives the same bytes. |
| 68 | #[test] |
| 69 | fn round_trips_every_opcode() { |
| 70 | for op in 0..=255u8 { |
| 71 | if op == 0xA5 { |
| 72 | continue; |
| 73 | } |
| 74 | let mut code = Box::new([0u8; 0x10000]); |
| 75 | code[0x100] = op; |
| 76 | code[0x101] = 0x30; |
| 77 | code[0x102] = 0x40; |
| 78 | let d = disassemble(&code, 0x100); |
| 79 | let src = format!("ORG 100h\n{}", d.text); |
| 80 | let out = assemble(&src); |
| 81 | assert!(out.ok(), "{op:02X} '{}': {:?}", d.text, out.diagnostics); |
| 82 | let bytes: Vec<u8> = out.bytes().map(|b| b.1).collect(); |
| 83 | assert_eq!( |
| 84 | bytes, |
| 85 | code[0x100..0x100 + d.size as usize], |
| 86 | "opcode {op:02X} '{}'", |
| 87 | d.text |
| 88 | ); |
| 89 | } |
| 90 | } |
| 91 | } |