package com.dreamfabric.jac64;
import java.io.ByteArrayOutputStream;
import java.util.Observable;
/**
* class C1541 - simple SerialBus and C1541 emulation.
* - will not support everything but the usual simplest
* CBM dos stuff. No fastloading, etc.
* Might add full 1541 emulation later with CPU + IO chip
* emulation.
*
* Created: Tue Mar 28 22:06:31 2006
*
* @author Joakim Eriksson, Dreamfabric.com
* joakime@sics.se
* @version 1.0
*/
public class C1541 extends Observable {
public static final int IO_OFFSET = CPU.IO_OFFSET;
public static final boolean DEBUG = true; //false;
public static final int SERIAL_ATN = (1 << 3);
public static final int SERIAL_CLK_OUT = (1 << 4);
public static final int SERIAL_DATA_OUT = (1 << 5);
public static final int SERIAL_CLK_IN = (1 << 6);
public static final int SERIAL_DATA_IN = (1 << 7);
public static final int TALK = 0x40;
public static final int LISTEN = 0x20;
public static final int DATA = 0x60;
public static final int OPEN = 0xf0;
public static final int CLOSE = 0xe0;
// Cpu memory
private int[] memory;
// Disk (.d64) reader
private C64Reader reader;
private static final int IDLE = 0;
private static final int ATN = 1;
private static final int RECEIVING = 2;
private static final int SENDING = 3;
private static final int READ_BIT = 4;
private static final int WAIT_BIT = 5;
private static final int READ_BYTE = 6;
private static final int WRITE_BYTE = 7;
private static final int LOAD_FILE = 1;
private static final int SAVE_FILE = 2;
private static final int LOGICAL_CHANNEL = 3;
private static final int ATN_SEEN = 10;
private static final int ATN_READ_BIT = 11;
private static final int ATN_WAIT_BIT = 12;
private static final int WAIT_LISTENER_READY = 13;
private static final int WRITE_BIT_CLK1 = 14;
private static final int WRITE_BIT_CLK2 = 15;
private static final int WRITE_END = 16;
private static final int WAIT_LISTENER_EOI_HANDSHAKE = 17;
private static final int READ_FILENAME = 1;
private DiskChannel channel[] = new DiskChannel[16];
private boolean atnLast = false;
private int mode = IDLE;
private long eoiTimeout = 0;
private int eoi;
private boolean lastChar = false;
private int role = 0;
private int floppyMode = 0;
private int floppyChannel = 0;
private String filename;
// For example when reading bytes and waits for EOI
private long waitTimeout = 0;
private int readMode = READ_FILENAME;
private String tmpFilename = "";
/**
* Creates a new <code>C1541</code> instance.
*
*/
public C1541(int[] memory) {
this.memory = memory;
reader = new C64Reader();
for (int i = 0, n = 16; i < n; i++) {
channel[i] = new DiskChannel(i);
}
}
public void reset() {
mode = IDLE;
role = 0;
rbState = 0;
rbByte = 0;
floppyMode = 0;
floppyChannel = 0;
clockHi();
}
public C64Reader getReader() {
return reader;
}
public void tick(long cycles) {
if (waitTimeout != 0 && waitTimeout < cycles) {
System.out.print(".");
tick(cycles, true);
}
}
public void tick(long cycles, boolean timeout) {
int data = memory[IO_OFFSET + 0xdd00];
boolean atn = (data & SERIAL_ATN) != 0;
boolean dataOut = (data & SERIAL_DATA_OUT) != 0;
boolean clkOut = (data & SERIAL_CLK_OUT) != 0;
boolean atnInvoked = atn & !atnLast;
switch (mode) {
// IDLE mode - just waiting... set data to high...
case READ_BYTE:
if (atnInvoked) {
mode = IDLE;
return;
}
int b = readByte(data, cycles, false, timeout);
if (b != 0) {
mode = IDLE;
if (atn) {
handleATNByte(b);
} else {
System.out.println("//// Read byte: " + Integer.toString(b, 16) +
" => " + ((char) b));
dataLo();
if (readMode == READ_FILENAME) {
tmpFilename += (char) b;
if (lastChar) {
System.out.println("Filename: " + tmpFilename);
filename = tmpFilename;
}
}
}
}
break;
case WRITE_BYTE:
if (atnInvoked) {
mode = IDLE;
return;
}
if (writeByte(data, cycles, timeout)) {
// When all is read - reset!
reset();
}
break;
case ATN_SEEN:
if (atn && !clkOut) {
dataHi();
mode = READ_BYTE;
// Start up for reading a byte...
readByte(data, cycles, true, false);
} else if (!atn) {
mode = IDLE;
}
break;
case IDLE:
if (atn & clkOut) {
System.out.println("C1541: ATN Seen...");
dataLo();
mode = ATN_SEEN;
}
if (!atn && role == TALK) {
// Here we should talk more...
if (!clkOut && dataOut) {
mode = WRITE_BYTE;
initWrite(cycles);
}
} else if (!atn && role != 0) {
if (!clkOut) {
mode = READ_BYTE;
readByte(data, cycles, true, false);
}
}
break;
}
atnLast = atn;
}
int rbState;
int rbByte;
int rbCtr = 0;
int eoiCtr = 0;
// Just pick bits from
private int readByte(int data, long cycles,
boolean restart, boolean timeout) {
boolean dataOut = (data & SERIAL_DATA_OUT) != 0;
boolean clkOut = (data & SERIAL_CLK_OUT) != 0;
if (restart) {
// Set everything to "low" and go...
rbCtr = 0;
rbByte = 0;
rbState = WAIT_BIT;
dataHi();
System.out.println("Start reading byte - data lo");
waitTimeout = 200 + cycles;
eoiCtr = 0;
lastChar = false;
return 0;
}
if (timeout) {
System.out.println("//// EOI Timeout???");
if (eoiCtr == 0) {
System.out.println("//// EOI 1 => dataLo");
dataLo();
waitTimeout = 80 + cycles;
} else {
System.out.println("///// EOI 2 => dataHi");
dataHi();
// No more timeout...
waitTimeout = 0;
lastChar = true;
}
eoiCtr++;
}
if (rbState == WAIT_BIT) {
if (clkOut)
rbState = READ_BIT;
} else {
if (!clkOut) {
rbByte |= dataOut ? 0 : (1 << rbCtr);
// System.out.println("//// Read bit: " + rbCtr + " => " + rbByte);
rbState = WAIT_BIT;
rbCtr++;
waitTimeout = 0;
}
}
if (rbCtr == 8) {
return rbByte;
}
return 0;
}
int wByte;
int wBitPos;
int wBytePos;
int wState;
long wCyclesWait;
byte[] bytesToWrite;
boolean wEOI = false;
private void initWriteByte(int data, long cycles) {
int b = data > ' ' ? data : '.';
if (DEBUG)
System.out.print("***>> InitW: " +
Integer.toString(data & 0xff, 16) + " '" + (char) b
+ "' ");
wByte = data;
wBitPos = 0;
// at least 100 us between the bytes
wCyclesWait = cycles + 100;
// Ensure that we get ticks!!!!
waitTimeout = cycles + 100;
wState = WAIT_LISTENER_READY;
}
private boolean writeByte(int data, long cycles, boolean timeout) {
boolean dataOut = (data & SERIAL_DATA_OUT) != 0;
// Do nothing if we are waiting for something...
if (wCyclesWait > cycles) {
// System.out.println("Waiting until: " + wCyclesWait + " now: " +
// cycles);
return false;
}
// System.out.println("wState:" + wState);
switch (wState) {
case WAIT_LISTENER_READY:
clockHi();
if (!dataOut) {
// If no file - exit here => file not found error...
if (bytesToWrite == null) {
waitTimeout = 0;
return true;
}
if (!wEOI) {
System.out.print("[R]");
wState = WRITE_BIT_CLK2;
} else {
System.out.print("[R(EOI)]");
wState = WAIT_LISTENER_EOI_HANDSHAKE;
}
// no wait for the first bit! (should call this method again)
} else
System.out.print("[-R]");
break;
case WAIT_LISTENER_EOI_HANDSHAKE:
if (dataOut) {
System.out.println("EOI handshake!!!");
wEOI = false;
wState = WAIT_LISTENER_READY;
}
break;
case WRITE_BIT_CLK1:
// C64 is waiting while the clock is low - as soon as clock
// is high it will read the data!
// Set bit and make clock low!
if ((wByte & (1 << wBitPos)) == 0) {
// System.out.println("* Write bit " + wBitPos + " low");
dataLo();
} else {
// System.out.println("* Write bit " + wBitPos + " high");
dataHi();
}
wBitPos++;
clockHi();
wCyclesWait = cycles + 70;
if (wBitPos < 8) {
wState = WRITE_BIT_CLK2;
} else {
wState = WRITE_END;
}
break;
case WRITE_BIT_CLK2:
// Set clock back to low - to indicate that another byte
// is coming up!
clockLo();
dataLo();
wCyclesWait = cycles + 70;
wState = WRITE_BIT_CLK1;
break;
case WRITE_END:
clockLo();
if (dataOut) {
System.out.println("Ack: " +
Integer.toString(memory[0xa4], 16));
wBytePos++;
if ((wBytePos % 10) == 0) {
setChanged();
notifyObservers("Loading " + filename + " " + (100 * wBytePos) /
bytesToWrite.length + "%");
}
if (wBytePos == bytesToWrite.length - 1) {
wEOI = true;
} else if (wBytePos >= bytesToWrite.length) {
waitTimeout = 0;
wEOI = false;
System.out.println("******** Write finished!!!");
setChanged();
notifyObservers("");
return true;
}
initWriteByte(bytesToWrite[wBytePos], cycles);
}
break;
}
return false;
}
private void initWrite(long cycles) {
// Do stuff with all sorts of things...
// Floppy channel, filename, etc.
// Filename
clockLo();
wBytePos = 0;
wEOI = false;
if (floppyMode == LOAD_FILE) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
if ((filename = reader.readFile(filename, -1, out)) != null) {
bytesToWrite = out.toByteArray();
System.out.println("C1541 have " + bytesToWrite.length +
" bytes to write");
initWriteByte(bytesToWrite[0], cycles);
} else {
// Error???
System.out.println("File not found... should signal error...");
bytesToWrite = null;
initWriteByte(0, cycles);
}
}
if (floppyMode == LOGICAL_CHANNEL) {
System.out.println("Should write logical channel data!");
bytesToWrite = channel[floppyChannel].getData();
initWriteByte(bytesToWrite[0], cycles);
}
}
private void handleATNByte(int data) {
int cmd = data & 0xf0;
int dev = data & 0x1f;
int secAdr = data & 0x0f;
System.out.println("ATN Byte: " + data + " " + Integer.toString(data, 16));
switch (cmd) {
case TALK:
case TALK + 0x10:
role = 0;
if (dev == 31) {
System.out.println(" >> UNTALK!!!");
} else {
System.out.println(" Received TALK for dev: " + dev);
if (dev == 8) {
System.out.println("### DEV: 8 ACTIVE as 1541!");
role = TALK;
}
}
break;
case LISTEN:
case LISTEN + 0x10:
role = 0;
if (dev == 31) {
System.out.println(" >> UNLISTEN!!!");
if (floppyMode == LOGICAL_CHANNEL) {
// Should load and set data too!
ByteArrayOutputStream out = new ByteArrayOutputStream();
if ((tmpFilename = reader.readFile(tmpFilename, -1, out)) != null) {
channel[floppyChannel].setData(out.toByteArray());
System.out.println("Setting channel " + floppyChannel +
" to " + tmpFilename + " size: " +
channel[floppyChannel].getData().length);
channel[floppyChannel].setFilename(tmpFilename);
filename = tmpFilename;
} else {
System.out.println("#### File not found error???");
}
}
} else {
System.out.println(" Received LISTEN for dev: " + dev);
if (dev == 8) {
System.out.println("### DEV: 8 ACTIVE as 1541!");
role = LISTEN;
}
}
break;
case OPEN:
System.out.println("### OPEN sec addr: " + secAdr);
tmpFilename = "";
readMode = READ_FILENAME;
if (secAdr == 0) {
System.out.println("### => LOAD File!");
floppyMode = LOAD_FILE;
} else if (secAdr == 1) {
System.out.println("### => SAVE File!");
floppyMode = SAVE_FILE;
readMode = READ_FILENAME;
} else if (secAdr == 15) {
System.out.println("### => Error...");
} else {
System.out.println("Logical channel: " + secAdr);
floppyMode = LOGICAL_CHANNEL;
floppyChannel = secAdr;
}
case CLOSE:
System.out.println("### Close: secAdr: " + secAdr);
channel[secAdr].close();
break;
case DATA:
System.out.println("### DATA sec addr: " + secAdr);
// Set current channel to this!
System.out.println("Setting floppy channel!");
floppyChannel = secAdr;
break;
}
}
private void clockLo() {
memory[IO_OFFSET + 0xdd00] &= ~SERIAL_CLK_IN;
}
private void clockHi() {
memory[IO_OFFSET + 0xdd00] |= SERIAL_CLK_IN;
}
public void dataLo() {
memory[IO_OFFSET + 0xdd00] &= ~SERIAL_DATA_IN;
}
public void dataHi() {
memory[IO_OFFSET + 0xdd00] |= SERIAL_DATA_IN;
}
public void handleDisk(int data, long cycles) {
// System.out.println("---- SerialBus: " + data + " ------");
if (DEBUG) {
System.out.print("EMU: ");
printSerial(data);
}
tick(cycles, false);
}
public static void printSerial(int data) {
if ((data & SERIAL_ATN) != 0) {
System.out.print("A1");
} else {
System.out.print("A0");
}
int sdata = (data & SERIAL_CLK_OUT) != 0 ? 1 : 0;
System.out.print(" C" + sdata);
sdata = (data & SERIAL_DATA_OUT) != 0 ? 1 : 0;
System.out.print(" D" + sdata);
sdata = (data & SERIAL_CLK_IN) != 0 ? 1 : 0;
System.out.print(" c" + sdata);
sdata = (data & SERIAL_DATA_IN) != 0 ? 1 : 0;
System.out.println(" d" + sdata + " (iec)");
}
}