From 129216383792936a7ccd5f3d4cdda519a4ef5ebe Mon Sep 17 00:00:00 2001 From: Adrian Conlon Date: Wed, 20 Feb 2019 22:23:29 +0000 Subject: [PATCH] Improve the Z80 disassembler: now does mostly good disassemblies. Signed-off-by: Adrian Conlon --- Z80/Disassembler.cs | 790 +++++++++++++++++++++++++++++++++++++++++ Z80/Disassembly.cs | 215 ----------- Z80/RefreshRegister.cs | 5 + Z80/Z80.csproj | 3 +- 4 files changed, 797 insertions(+), 216 deletions(-) create mode 100644 Z80/Disassembler.cs delete mode 100644 Z80/Disassembly.cs diff --git a/Z80/Disassembler.cs b/Z80/Disassembler.cs new file mode 100644 index 0000000..8d5335a --- /dev/null +++ b/Z80/Disassembler.cs @@ -0,0 +1,790 @@ +namespace EightBit +{ + using System.Text; + + public class Disassembler + { + private readonly Bus bus; + + private bool prefixCB = false; + private bool prefixDD = false; + private bool prefixED = false; + private bool prefixFD = false; + + public Disassembler(Bus bus) + { + this.bus = bus; + } + + public Bus Bus => this.bus; + + public static string AsFlag(byte value, StatusBits flag, string represents) => (value & (byte)flag) != 0 ? represents : "-"; + + public static string AsFlags(byte value) + { + return $"{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) + { + 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} " + + $"I={i:X2} R={(byte)r:X2} " + + $"IM={im}"; + } + + public string Disassemble(Z80 cpu) + { + this.prefixCB = this.prefixDD = this.prefixED = this.prefixFD = false; + return this.Disassemble(cpu, cpu.PC().Word); + } + + private static string CC(int flag) + { + switch (flag) + { + case 0: + return "NZ"; + case 1: + return "Z"; + case 2: + return "NC"; + case 3: + return "C"; + case 4: + return "PO"; + case 5: + return "PE"; + case 6: + return "P"; + case 7: + return "M"; + } + + throw new System.ArgumentOutOfRangeException(nameof(flag)); + } + + private static string ALU(int which) + { + switch (which) + { + case 0: // ADD A,n + return "ADD"; + case 1: // ADC + return "ADC"; + case 2: // SUB n + return "SUB"; + case 3: // SBC A,n + return "SBC"; + case 4: // AND n + return "AND"; + case 5: // XOR n + return "XOR"; + case 6: // OR n + return "OR"; + case 7: // CP n + return "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}"; + + string specification = string.Empty; + if (this.prefixCB) + { + output += this.DisassembleCB(cpu, pc, ref specification, ref dumpCount, x, y, z, p, q); + } + else if (this.prefixED) + { + output += this.DisassembleED(cpu, pc, 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 (int 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(Z80 cpu, ushort pc, ref string specification, ref int dumpCount, int x, int y, int z, int p, int q) + { + string 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(Z80 cpu, ushort pc, ref string specification, ref int dumpCount, int x, int y, int z, int p, int q) + { + string 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:X4H)," + this.RP(p); + break; + case 1: // LD rp,(nn) + specification = "LD " + this.RP(p) + ",(%2$04XH)"; + break; + } + + dumpCount += 2; + 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) + { + string 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 + if (z == 6 && y == 6) + { + specification = "HALT"; // Exception (replaces LD (HL), (HL)) + } + else + { + specification = $"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)); + } + } +} diff --git a/Z80/Disassembly.cs b/Z80/Disassembly.cs deleted file mode 100644 index cf78080..0000000 --- a/Z80/Disassembly.cs +++ /dev/null @@ -1,215 +0,0 @@ -namespace Z80 -{ - using EightBit; - using System.Text; - - public class Disassembly - { - private bool prefixCB = false; - private bool prefixDD = false; - private bool prefixED = false; - private bool prefixFD = false; - private readonly Bus bus; - - public Disassembly(Bus bus) - { - this.bus = bus; - } - - public Bus Bus => this.bus; - - public static string State(Z80 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} SP={sp} " - + $"A={AsHex(a)} F={AsFlags(f)} " - + $"B={AsHex(b)} C={AsHex(c)} " - + $"D={AsHex(d)} E={AsHex(e)} " - + $"H={AsHex(h)} L={AsHex(l)} " - + $"I={AsHex(i)} R={AsHex(r)} " - + $"IM={im}"; - } - - public string Disassemble(Z80 cpu) - { - this.prefixCB = this.prefixDD = this.prefixED = this.prefixFD = false; - return Disassemble(cpu, cpu.PC().Word); - } - - public static string flag(byte value, int flag, string represents) - { - return ""; - } - - public static string AsFlags(byte value) - { - return ""; - } - - public static string AsHex(byte value) - { - return ""; - } - - public static string AsHex(ushort value) - { - return ""; - } - - public static string AsBinary(byte value) - { - return ""; - } - - public static string AsDecimal(byte value) - { - return ""; - } - - public static string AsInvalid(byte value) - { - return ""; - } - - private string Disassemble(Z80 cpu, ushort pc) - { - var opCode = 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 = Bus.Peek((ushort)(pc + 1)); - var absolute = cpu.PeekWord((ushort)(pc + 1)).Word; - var displacement = (sbyte)immediate; - var relative = pc + displacement + 2; - var indexedImmediate = Bus.Peek((ushort)(pc + 1)); - - var dumpCount = 0; - - var output = $"{AsHex(opCode)}"; - - var specification = ""; - - if (this.prefixCB) - output += this.DisassembleCB( - cpu, pc, - specification, ref dumpCount, - x, y, z, p, q); - else if (this.prefixED) - output += this.DisassembleED( - cpu, pc, - specification, ref dumpCount, - x, y, z, p, q); - else - output += this.DisassembleOther( - cpu, pc, - specification, ref dumpCount, - x, y, z, p, q); - - for (int i = 0; i < dumpCount; ++i) - output += $"{AsHex(this.Bus.Peek((ushort)(pc + i + 1)))}"; - - var outputFormatSpecification = !this.prefixDD; - if (this.prefixDD) - { - if (opCode != 0xdd) - { - outputFormatSpecification = true; - } - } - - if (outputFormatSpecification) - { - output += '\t'; - //m_formatter.parse(specification); - //output << m_formatter % (int)immediate % (int)absolute % relative % (int)displacement % indexedImmediate; - } - - return output; - } - - private string DisassembleCB( - Z80 cpu, - ushort pc, - string specification, - ref int dumpCount, - int x, int y, int z, - int p, int q) - { - return ""; - } - - private string DisassembleED( - Z80 cpu, - ushort pc, - string specification, - ref int dumpCount, - int x, int y, int z, - int p, int q) - { - return ""; - } - - private string DisassembleOther( - Z80 cpu, - ushort pc, - string specification, - ref int dumpCount, - int x, int y, int z, - int p, int q) - { - return ""; - } - - private string RP(int rp) - { - return ""; - } - - private string RP2(int rp) - { - return ""; - } - - private string R(int r) - { - return ""; - } - - private static string CC(int flag) - { - return ""; - } - - private static string ALU(int which) - { - return ""; - } - } -} diff --git a/Z80/RefreshRegister.cs b/Z80/RefreshRegister.cs index 255a85d..13ba655 100644 --- a/Z80/RefreshRegister.cs +++ b/Z80/RefreshRegister.cs @@ -37,6 +37,11 @@ return new RefreshRegister(input); } + public byte ToByte() + { + return ToByte(this); + } + public static RefreshRegister Increment(RefreshRegister value) { ++value.variable; diff --git a/Z80/Z80.csproj b/Z80/Z80.csproj index 045785c..39ecf8f 100644 --- a/Z80/Z80.csproj +++ b/Z80/Z80.csproj @@ -21,6 +21,7 @@ DEBUG;TRACE prompt 4 + latest pdbonly @@ -44,7 +45,7 @@ - +