EightBitNet/Z80/Disassembler.cs

794 lines
30 KiB
C#

// <copyright file="Disassembler.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
namespace EightBit
{
using System;
public class Disassembler(Bus bus)
{
private bool prefixCB = false;
private bool prefixDD = false;
private bool prefixED = false;
private bool prefixFD = false;
public Bus Bus { get; } = bus;
public static string AsFlag(byte value, StatusBits flag, string represents) => (value & (byte)flag) != 0 ? represents : "-";
public static string AsFlags(byte value) =>
$"{AsFlag(value, StatusBits.SF, "S")}"
+ $"{AsFlag(value, StatusBits.ZF, "Z")}"
+ $"{AsFlag(value, StatusBits.YF, "Y")}"
+ $"{AsFlag(value, StatusBits.HC, "H")}"
+ $"{AsFlag(value, StatusBits.XF, "X")}"
+ $"{AsFlag(value, StatusBits.PF, "P")}"
+ $"{AsFlag(value, StatusBits.NF, "N")}"
+ $"{AsFlag(value, StatusBits.CF, "C")}";
public static string State(Z80 cpu)
{
ArgumentNullException.ThrowIfNull(cpu);
var pc = cpu.PC;
var sp = cpu.SP;
var a = cpu.A;
var f = cpu.F;
var b = cpu.B;
var c = cpu.C;
var d = cpu.D;
var e = cpu.E;
var h = cpu.H;
var l = cpu.L;
var i = cpu.IV;
var r = cpu.REFRESH;
var im = cpu.IM;
return
$"PC={pc.Word:x4} SP={sp.Word:x4} "
+ $"A={a:x2} F={AsFlags(f)} "
+ $"B={b:x2} C={c:x2} "
+ $"D={d:x2} E={e:x2} "
+ $"H={h:x2} L={l:x2} "
+ $"IX={cpu.IX.Word:x4} "
+ $"IY={cpu.IY.Word:x4} "
+ $"I={i:x2} R={(byte)r:x2} "
+ $"IM={im} "
+ $"IFF1={(cpu.IFF1 ? 1 : 0)} "
+ $"{(cpu.RESET.Lowered() ? "R" : "-")}"
+ $"{(cpu.INT.Lowered() ? "I" : "-")}"
+ $"{(cpu.HALT.Lowered() ? "H" : "-")}"
+ $"{(cpu.NMI.Lowered() ? "N" : "-")}";
}
public string Disassemble(Z80 cpu)
{
ArgumentNullException.ThrowIfNull(cpu);
this.prefixCB = this.prefixDD = this.prefixED = this.prefixFD = false;
return this.Disassemble(cpu, cpu.PC.Word);
}
private static string CC(int flag) => flag switch
{
0 => "NZ",
1 => "Z",
2 => "NC",
3 => "C",
4 => "PO",
5 => "PE",
6 => "P",
7 => "M",
_ => throw new System.ArgumentOutOfRangeException(nameof(flag)),
};
private static string ALU(int which) => which switch
{
0 => "ADD",
1 => "ADC",
2 => "SUB",
3 => "SBC",
4 => "AND",
5 => "XOR",
6 => "OR",
7 => "CP",
_ => throw new System.ArgumentOutOfRangeException(nameof(which)),
};
private string Disassemble(Z80 cpu, ushort pc)
{
var opCode = this.Bus.Peek(pc);
var decoded = cpu.GetDecodedOpCode(opCode);
var x = decoded.X;
var y = decoded.Y;
var z = decoded.Z;
var p = decoded.P;
var q = decoded.Q;
var immediate = this.Bus.Peek((ushort)(pc + 1));
var absolute = cpu.PeekWord((ushort)(pc + 1)).Word;
var displacement = (sbyte)immediate;
var relative = pc + displacement + 2;
var indexedImmediate = this.Bus.Peek((ushort)(pc + 1));
var dumpCount = 0;
var output = $"{opCode:x2}";
var specification = string.Empty;
if (this.prefixCB)
{
output += this.DisassembleCB(ref specification, x, y, z);
}
else if (this.prefixED)
{
output += this.DisassembleED(ref specification, ref dumpCount, x, y, z, p, q);
}
else
{
output += this.DisassembleOther(cpu, pc, ref specification, ref dumpCount, x, y, z, p, q);
}
for (var i = 0; i < dumpCount; ++i)
{
output += $"{this.Bus.Peek((ushort)(pc + i + 1)):x2}";
}
var outputFormatSpecification = !this.prefixDD;
if (this.prefixDD)
{
if (opCode != 0xdd)
{
outputFormatSpecification = true;
}
}
if (outputFormatSpecification)
{
output += '\t';
output += string.Format(specification, (int)immediate, (int)absolute, relative, (int)displacement, indexedImmediate);
}
return output;
}
private string DisassembleCB(ref string specification, int x, int y, int z)
{
var output = string.Empty;
switch (x)
{
case 0: // rot[y] r[z]
switch (y)
{
case 0:
specification = $"RLC {this.R(z)}";
break;
case 1:
specification = $"RRC {this.R(z)}";
break;
case 2:
specification = $"RL {this.R(z)}";
break;
case 3:
specification = $"RR {this.R(z)}";
break;
case 4:
specification = $"SLA {this.R(z)}";
break;
case 5:
specification = $"SRA {this.R(z)}";
break;
case 6:
specification = $"SWAP {this.R(z)}";
break;
case 7:
specification = $"SRL {this.R(z)}";
break;
}
break;
case 1: // BIT y, r[z]
specification = $"BIT {y},{this.R(z)}";
break;
case 2: // RES y, r[z]
specification = $"RES {y},{this.R(z)}";
break;
case 3: // SET y, r[z]
specification = $"SET {y},{this.R(z)}";
break;
}
return output;
}
private string DisassembleED(ref string specification, ref int dumpCount, int x, int y, int z, int p, int q)
{
var output = string.Empty;
switch (x)
{
case 0:
case 3:
specification = "NONI NOP";
break;
case 1:
switch (z)
{
case 2:
switch (q)
{
case 0: // SBC HL,rp
specification = $"SBC HL,{this.RP(p)}";
break;
case 1: // ADC HL,rp
specification = $"ADC HL,{this.RP(p)}";
break;
}
break;
case 3:
switch (q)
{
case 0: // LD (nn),rp
specification = "LD ({1:X4}H)," + this.RP(p);
break;
case 1: // LD rp,(nn)
specification = "LD " + this.RP(p) + ",(%2$04XH)";
break;
}
dumpCount += 2;
break;
case 4: // Negate accumulator
specification = "NEG";
break;
case 5: // Return from interrupt
specification = y switch
{
1 => "RETI",
_ => "RETN",
};
break;
case 6: // Set interrupt mode
switch (y)
{
case 0:
case 1:
case 4:
case 5:
specification = "IM 0";
break;
case 2:
case 6:
specification = "IM 1";
break;
case 3:
case 7:
specification = "IM 2";
break;
}
break;
case 7:
switch (y)
{
case 0:
specification = "LD I,A";
break;
case 1:
specification = "LD R,A";
break;
case 2:
specification = "LD A,I";
break;
case 3:
specification = "LD A,R";
break;
case 4:
specification = "RRD";
break;
case 5:
specification = "RLD";
break;
case 6:
case 7:
specification = "NOP";
break;
}
break;
}
break;
case 2:
switch (z)
{
case 0: // LD
switch (y)
{
case 4: // LDI
specification = "LDI";
break;
case 5: // LDD
specification = "LDD";
break;
case 6: // LDIR
specification = "LDIR";
break;
case 7: // LDDR
specification = "LDDR";
break;
}
break;
case 1: // CP
switch (y)
{
case 4: // CPI
specification = "CPI";
break;
case 5: // CPD
specification = "CPD";
break;
case 6: // CPIR
specification = "CPIR";
break;
case 7: // CPDR
specification = "CPDR";
break;
}
break;
case 2: // IN
switch (y)
{
case 4: // INI
specification = "INI";
break;
case 5: // IND
specification = "IND";
break;
case 6: // INIR
specification = "INIR";
break;
case 7: // INDR
specification = "INDR";
break;
}
break;
case 3: // OUT
switch (y)
{
case 4: // OUTI
specification = "OUTI";
break;
case 5: // OUTD
specification = "OUTD";
break;
case 6: // OTIR
specification = "OTIR";
break;
case 7: // OTDR
specification = "OTDR";
break;
}
break;
}
break;
}
return output;
}
private string DisassembleOther(Z80 cpu, ushort pc, ref string specification, ref int dumpCount, int x, int y, int z, int p, int q)
{
var output = string.Empty;
switch (x)
{
case 0:
switch (z)
{
case 0: // Relative jumps and assorted ops
switch (y)
{
case 0: // NOP
specification = "NOP";
break;
case 1: // EX AF AF'
specification = "EX AF AF'";
break;
case 2: // DJNZ d
specification = "DJNZ {2:X4}H";
dumpCount += 2;
break;
case 3: // JR d
specification = "JR {2:X4}H";
dumpCount++;
break;
default: // JR cc,d
specification = "JR " + CC(y - 4) + ",{2:X4}H";
dumpCount++;
break;
}
break;
case 1: // 16-bit load immediate/add
switch (q)
{
case 0: // LD rp,nn
specification = "LD " + this.RP(p) + ",{1:X4}H";
dumpCount += 2;
break;
case 1: // ADD HL,rp
specification = $"ADD HL,{this.RP(p)}";
break;
}
break;
case 2: // Indirect loading
switch (q)
{
case 0:
switch (p)
{
case 0: // LD (BC),A
specification = "LD (BC),A";
break;
case 1: // LD (DE),A
specification = "LD (DE),A";
break;
case 2: // LD (nn),HL
specification = "LD ({1:X4}H),HL";
dumpCount += 2;
break;
case 3: // LD (nn),A
specification = "LD ({1:X4}H),A";
dumpCount += 2;
break;
}
break;
case 1:
switch (p)
{
case 0: // LD A,(BC)
specification = "LD A,(BC)";
break;
case 1: // LD A,(DE)
specification = "LD A,(DE)";
break;
case 2: // LD HL,(nn)
specification = "LD HL,({1:X4}H)";
dumpCount += 2;
break;
case 3: // LD A,(nn)
specification = "LD A,({1:X4}H)";
dumpCount += 2;
break;
}
break;
}
break;
case 3: // 16-bit INC/DEC
switch (q)
{
case 0: // INC rp
specification = $"INC {this.RP(p)}";
break;
case 1: // DEC rp
specification = $"DEC {this.RP(p)}";
break;
}
break;
case 4: // 8-bit INC
specification = $"INC {this.R(y)}";
break;
case 5: // 8-bit DEC
specification = $"DEC {this.R(y)}";
break;
case 6: // 8-bit load immediate
specification = $"LD {this.R(y)}";
if (y == 6 && (this.prefixDD || this.prefixFD))
{
specification += ",{4:X2}H";
dumpCount++;
}
else
{
specification += ",{0:X2}H";
}
dumpCount++;
break;
case 7: // Assorted operations on accumulator/flags
switch (y)
{
case 0:
specification = "RLCA";
break;
case 1:
specification = "RRCA";
break;
case 2:
specification = "RLA";
break;
case 3:
specification = "RRA";
break;
case 4:
specification = "DAA";
break;
case 5:
specification = "CPL";
break;
case 6:
specification = "SCF";
break;
case 7:
specification = "CCF";
break;
}
break;
}
break;
case 1: // 8-bit loading
specification = z == 6 && y == 6 ? "HALT" : $"LD {this.R(y)},{this.R(z)}";
break;
case 2: // Operate on accumulator and register/memory location
specification = $"{ALU(y)} A,{this.R(z)}";
break;
case 3:
switch (z)
{
case 0: // Conditional return
specification = $"RET {CC(y)}";
break;
case 1: // POP & various ops
switch (q)
{
case 0: // POP rp2[p]
specification = $"POP {this.RP2(p)}";
break;
case 1:
switch (p)
{
case 0: // RET
specification = "RET";
break;
case 1: // EXX
specification = "EXX";
break;
case 2: // JP (HL)
specification = "JP (HL)";
break;
case 3: // LD SP,HL
specification = "LD SP,HL";
break;
}
break;
}
break;
case 2: // Conditional jump
specification = $"JP {CC(y)}" + ",{1:X4}H";
dumpCount += 2;
break;
case 3: // Assorted operations
switch (y)
{
case 0: // JP nn
specification = "JP {1:X4}H";
dumpCount += 2;
break;
case 1: // CB prefix
this.prefixCB = true;
output += this.Disassemble(cpu, ++pc);
break;
case 2: // OUT (n),A
specification = "OUT ({0:X2}H),A";
dumpCount++;
break;
case 3: // IN A,(n)
specification = "IN A,({0:X2}H)";
dumpCount++;
break;
case 4: // EX (SP),HL
specification = "EX (SP),HL";
break;
case 5: // EX DE,HL
specification = "EX DE,HL";
break;
case 6: // DI
specification = "DI";
break;
case 7: // EI
specification = "EI";
break;
}
break;
case 4: // Conditional call: CALL cc[y], nn
specification = $"CALL {CC(y)}" + ",{1:X4}H";
dumpCount += 2;
break;
case 5: // PUSH & various ops
switch (q)
{
case 0: // PUSH rp2[p]
specification = $"PUSH {this.RP2(p)}";
break;
case 1:
switch (p)
{
case 0: // CALL nn
specification = "CALL {1:X4}H";
dumpCount += 2;
break;
case 1: // DD prefix
this.prefixDD = true;
output += this.Disassemble(cpu, ++pc);
break;
case 2: // ED prefix
this.prefixED = true;
output += this.Disassemble(cpu, ++pc);
break;
case 3: // FD prefix
this.prefixFD = true;
output += this.Disassemble(cpu, ++pc);
break;
}
break;
}
break;
case 6: // Operate on accumulator and immediate operand: alu[y] n
specification = ALU(y) + " A,{0:X2}H";
dumpCount++;
break;
case 7: // Restart: RST y * 8
specification = $"RST {y * 8:X2}";
break;
}
break;
}
return output;
}
private string RP(int rp)
{
switch (rp)
{
case 0:
return "BC";
case 1:
return "DE";
case 2:
if (this.prefixDD)
{
return "IX";
}
if (this.prefixFD)
{
return "IY";
}
return "HL";
case 3:
return "SP";
}
throw new System.ArgumentOutOfRangeException(nameof(rp));
}
private string RP2(int rp)
{
switch (rp)
{
case 0:
return "BC";
case 1:
return "DE";
case 2:
if (this.prefixDD)
{
return "IX";
}
if (this.prefixFD)
{
return "IY";
}
return "HL";
case 3:
return "AF";
}
throw new System.ArgumentOutOfRangeException(nameof(rp));
}
private string R(int r)
{
switch (r)
{
case 0:
return "B";
case 1:
return "C";
case 2:
return "D";
case 3:
return "E";
case 4:
if (this.prefixDD)
{
return "IXH";
}
if (this.prefixFD)
{
return "IYH";
}
return "H";
case 5:
if (this.prefixDD)
{
return "IXL";
}
if (this.prefixFD)
{
return "IYL";
}
return "L";
case 6:
if (this.prefixDD || this.prefixFD)
{
if (this.prefixDD)
{
return "IX+{4}";
}
if (this.prefixFD)
{
return "IY+{4}";
}
}
else
{
return "(HL)";
}
break;
case 7:
return "A";
}
throw new System.ArgumentOutOfRangeException(nameof(r));
}
}
}