kopia lustrzana https://github.com/maccasoft/z80-tools
387 wiersze
11 KiB
Java
387 wiersze
11 KiB
Java
/*
|
|
* Copyright (c) 2020 Marco Maccaferri and others.
|
|
* All rights reserved.
|
|
*
|
|
* This program and the accompanying materials are made available under
|
|
* the terms of the Eclipse Public License v1.0 which accompanies this
|
|
* distribution, and is available at
|
|
* http://www.eclipse.org/legal/epl-v10.html
|
|
*/
|
|
|
|
package com.maccasoft.tools;
|
|
|
|
import java.io.PrintStream;
|
|
|
|
import com.maccasoft.tools.SourceMap.LineEntry;
|
|
|
|
import nl.grauw.glass.Source;
|
|
import z80core.MemIoOps;
|
|
import z80core.NotifyOps;
|
|
import z80core.Z80;
|
|
|
|
public class Debugger extends MemIoOps implements NotifyOps {
|
|
|
|
Z80 proc;
|
|
SourceMap sourceMap;
|
|
|
|
DebugTerminal debugTerminal;
|
|
|
|
int tms9918Ram;
|
|
int tms9918Reg;
|
|
TMS9918 tms9918;
|
|
|
|
boolean stop;
|
|
|
|
final PrintStream out;
|
|
|
|
public Debugger(PrintStream out) {
|
|
super(65536);
|
|
|
|
this.out = out;
|
|
|
|
z80Ram[0] = (byte) 0xC3;
|
|
z80Ram[1] = 0x00;
|
|
z80Ram[2] = 0x01; // JP 0x100 CP/M TPA
|
|
z80Ram[5] = (byte) 0xC9; // Return from BDOS call
|
|
|
|
z80Ram[8] = (byte) 0xD3; // RST08
|
|
z80Ram[9] = (byte) 0x81;
|
|
z80Ram[10] = (byte) 0xC9;
|
|
|
|
tms9918Ram = 0x40;
|
|
tms9918Reg = 0x41;
|
|
tms9918 = new TMS9918();
|
|
|
|
proc = new Z80(this, this);
|
|
proc.setBreakpoint(0x0005, true);
|
|
}
|
|
|
|
public void setSource(Source source) {
|
|
sourceMap = new SourceMap(source, this);
|
|
sourceMap.build();
|
|
|
|
proc.setPinReset();
|
|
proc.reset();
|
|
proc.setRegPC(sourceMap.getEntryAddress());
|
|
}
|
|
|
|
public SourceMap getSourceMap() {
|
|
return sourceMap;
|
|
}
|
|
|
|
public void setDebugTerminal(DebugTerminal debugTerminal) {
|
|
this.debugTerminal = debugTerminal;
|
|
}
|
|
|
|
@Override
|
|
public void reset() {
|
|
tms9918.reset();
|
|
|
|
proc.setPinReset();
|
|
proc.reset();
|
|
proc.setRegPC(sourceMap.getEntryAddress());
|
|
|
|
super.reset();
|
|
}
|
|
|
|
@Override
|
|
public int inPort(int port) {
|
|
tstates += 4; // 4 clocks for read byte from bus
|
|
|
|
port &= 0xFF;
|
|
|
|
switch (port) {
|
|
case Machine.SIOA_C: {
|
|
int result = 0b00101100; // TX Buffer Empty, DCD and CTS
|
|
try {
|
|
if (debugTerminal != null && debugTerminal.getInputStream().available() > 0) {
|
|
result |= 0x01; // RX Char Available
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
return result;
|
|
}
|
|
case Machine.SIOA_D: {
|
|
try {
|
|
if (debugTerminal != null && debugTerminal.getInputStream().available() > 0) {
|
|
return debugTerminal.getInputStream().read();
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
return 0x00;
|
|
}
|
|
case Machine.SIOB_C:
|
|
return 0b00101100; // TX Buffer Empty, DCD and CTS
|
|
case Machine.SIOB_D:
|
|
return 0x00;
|
|
}
|
|
|
|
if (port == tms9918Ram) {
|
|
return tms9918.inRam();
|
|
}
|
|
if (port == tms9918Reg) {
|
|
return tms9918.inReg();
|
|
}
|
|
|
|
return port;
|
|
}
|
|
|
|
@Override
|
|
public void outPort(int port, int value) {
|
|
tstates += 4; // 4 clocks for write byte to bus
|
|
|
|
port &= 0xFF;
|
|
value &= 0xFF;
|
|
|
|
switch (port) {
|
|
case Machine.SIOA_D:
|
|
if (debugTerminal != null) {
|
|
debugTerminal.write(value);
|
|
}
|
|
else {
|
|
out.write(value);
|
|
}
|
|
break;
|
|
case Machine.SIOB_D:
|
|
out.write(value);
|
|
break;
|
|
}
|
|
|
|
if (port == tms9918Ram) {
|
|
tms9918.outRam(value);
|
|
}
|
|
if (port == tms9918Reg) {
|
|
tms9918.outReg(value);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int breakpoint(int address, int opcode) {
|
|
|
|
// Emulate CP/M Syscall at address 5
|
|
if (address == 0x0005) {
|
|
if (z80Ram[5] != (byte) 0xC9) {
|
|
return opcode;
|
|
}
|
|
doCPMEmulation();
|
|
}
|
|
|
|
return opcode;
|
|
}
|
|
|
|
protected void doCPMEmulation() {
|
|
switch (proc.getRegC()) {
|
|
case 0x00: // BDOS 0 System Reset
|
|
stop = true;
|
|
out.println("Z80 reset after " + getTstates() + " t-states");
|
|
break;
|
|
case 0x01: // BDOS 1 console char input
|
|
try {
|
|
if (debugTerminal != null && debugTerminal.getInputStream().available() > 0) {
|
|
proc.setRegA(debugTerminal.getInputStream().read());
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
break;
|
|
case 0x02: // BDOS 2 console char output
|
|
if (debugTerminal != null) {
|
|
debugTerminal.write(proc.getRegE());
|
|
}
|
|
else {
|
|
out.write(proc.getRegE());
|
|
}
|
|
break;
|
|
case 0x04: // BDOS 4 punch output
|
|
case 0x05: // BDOS 2 list output
|
|
out.write(proc.getRegE());
|
|
break;
|
|
case 0x06: { // BDOS 6 direct console I/O
|
|
if (proc.getRegE() == 0xFF) {
|
|
try {
|
|
if (debugTerminal != null && debugTerminal.getInputStream().available() > 0) {
|
|
proc.setRegA(debugTerminal.getInputStream().read());
|
|
}
|
|
else {
|
|
proc.setRegA(0);
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
else {
|
|
if (debugTerminal != null) {
|
|
debugTerminal.write(proc.getRegE());
|
|
}
|
|
else {
|
|
out.write(proc.getRegE());
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 0x09: { // BDOS 9 console string output (string terminated by "$")
|
|
int strAddr = proc.getRegDE();
|
|
if (debugTerminal != null) {
|
|
while (peek8(strAddr) != '$') {
|
|
debugTerminal.write(peek8(strAddr++));
|
|
}
|
|
}
|
|
else {
|
|
while (peek8(strAddr) != '$') {
|
|
out.write(peek8(strAddr++));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 0x0B: // BDOS 11 console status
|
|
try {
|
|
if (debugTerminal != null && debugTerminal.getInputStream().available() > 0) {
|
|
proc.setRegA(0xFF);
|
|
}
|
|
else {
|
|
proc.setRegA(0x00);
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
break;
|
|
default:
|
|
out.println("BDOS Call " + proc.getRegC());
|
|
break;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void execDone() {
|
|
}
|
|
|
|
public void doStop() {
|
|
stop = true;
|
|
}
|
|
|
|
public void stepOver() {
|
|
LineEntry lineEntry = sourceMap.getLineAtAddress(proc.getRegPC());
|
|
if (lineEntry != null) {
|
|
int stepOverPC1 = proc.getRegPC() + lineEntry.code.length;
|
|
int stepOverPC2 = peek16(proc.getRegSP());
|
|
int stepOverSP = proc.getRegSP();
|
|
|
|
stop = false;
|
|
do {
|
|
int currentPC = proc.getRegPC();
|
|
proc.execute();
|
|
|
|
if (isBreakpoint(proc.getRegPC())) {
|
|
break;
|
|
}
|
|
if (proc.getRegPC() == stepOverPC1 || proc.getRegPC() == stepOverPC2 || (proc.getRegPC() != currentPC && proc.getRegSP() == stepOverSP)) {
|
|
break;
|
|
}
|
|
} while (!stop);
|
|
|
|
}
|
|
else {
|
|
proc.execute();
|
|
}
|
|
tms9918.redrawFrame();
|
|
}
|
|
|
|
public void stepInto() {
|
|
LineEntry lineEntry = sourceMap.getLineAtAddress(proc.getRegPC());
|
|
if (lineEntry != null) {
|
|
int stepOverPC1 = proc.getRegPC() + lineEntry.code.length;
|
|
int stepOverPC2 = peek16(proc.getRegSP());
|
|
int stepOverSP = proc.getRegSP();
|
|
|
|
proc.execute();
|
|
|
|
if (sourceMap.getLineAtAddress(proc.getRegPC()) == null) {
|
|
stop = false;
|
|
do {
|
|
int currentPC = proc.getRegPC();
|
|
proc.execute();
|
|
|
|
if (isBreakpoint(proc.getRegPC())) {
|
|
break;
|
|
}
|
|
if (proc.getRegPC() == stepOverPC1 || proc.getRegPC() == stepOverPC2 || (proc.getRegPC() != currentPC && proc.getRegSP() == stepOverSP)) {
|
|
break;
|
|
}
|
|
} while (!stop);
|
|
}
|
|
}
|
|
else {
|
|
proc.execute();
|
|
}
|
|
tms9918.redrawFrame();
|
|
}
|
|
|
|
public void run() {
|
|
LineEntry lineEntry = sourceMap.getLineAtAddress(proc.getRegPC());
|
|
if (lineEntry != null) {
|
|
int stepOverPC1 = proc.getRegPC() + lineEntry.code.length;
|
|
int stepOverPC2 = peek16(proc.getRegSP());
|
|
int stepOverSP = proc.getRegSP();
|
|
|
|
stop = false;
|
|
do {
|
|
proc.execute();
|
|
|
|
lineEntry = sourceMap.getLineAtAddress(proc.getRegPC());
|
|
if (lineEntry != null) {
|
|
stepOverPC1 = proc.getRegPC() + lineEntry.code.length;
|
|
stepOverPC2 = peek16(proc.getRegSP());
|
|
stepOverSP = proc.getRegSP();
|
|
}
|
|
|
|
if (isBreakpoint(proc.getRegPC())) {
|
|
break;
|
|
}
|
|
} while (!stop);
|
|
|
|
if (sourceMap.getLineAtAddress(proc.getRegPC()) == null) {
|
|
stop = false;
|
|
do {
|
|
int currentPC = proc.getRegPC();
|
|
proc.execute();
|
|
|
|
if (isBreakpoint(proc.getRegPC())) {
|
|
break;
|
|
}
|
|
if (proc.getRegPC() == stepOverPC1 || proc.getRegPC() == stepOverPC2 || (proc.getRegPC() != currentPC && proc.getRegSP() == stepOverSP)) {
|
|
break;
|
|
}
|
|
} while (!stop);
|
|
}
|
|
}
|
|
|
|
tms9918.redrawFrame();
|
|
}
|
|
|
|
public void runToAddress(int addr) {
|
|
stop = false;
|
|
do {
|
|
proc.execute();
|
|
|
|
if (isBreakpoint(proc.getRegPC())) {
|
|
break;
|
|
}
|
|
if (proc.getRegPC() == addr) {
|
|
break;
|
|
}
|
|
} while (!stop);
|
|
tms9918.redrawFrame();
|
|
}
|
|
|
|
protected boolean isBreakpoint(int address) {
|
|
return false;
|
|
}
|
|
|
|
public void resetBreakpoints() {
|
|
proc.resetBreakpoints();
|
|
proc.setBreakpoint(0x0005, true);
|
|
}
|
|
}
|