2019-01-26 07:53:09 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2018-19 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.File;
|
|
|
|
import java.io.FileInputStream;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.io.RandomAccessFile;
|
|
|
|
|
2020-08-08 10:14:43 +00:00
|
|
|
import com.maccasoft.tools.internal.Utility;
|
|
|
|
|
2019-01-26 07:53:09 +00:00
|
|
|
import z80core.MemIoOps;
|
|
|
|
import z80core.Z80;
|
|
|
|
|
|
|
|
public class Machine extends MemIoOps {
|
|
|
|
|
|
|
|
public final static int SIOA_C = 0x80;
|
|
|
|
public final static int SIOA_D = 0x81;
|
|
|
|
public final static int SIOB_C = 0x82;
|
|
|
|
public final static int SIOB_D = 0x83;
|
|
|
|
|
|
|
|
public final static int CF_DATA = 0x10;
|
|
|
|
public final static int CF_FEATURES = 0x11;
|
|
|
|
public final static int CF_ERROR = 0x11;
|
|
|
|
public final static int CF_SECCOUNT = 0x12;
|
|
|
|
public final static int CF_SECTOR = 0x13;
|
|
|
|
public final static int CF_CYL_LOW = 0x14;
|
|
|
|
public final static int CF_CYL_HI = 0x15;
|
|
|
|
public final static int CF_HEAD = 0x16;
|
|
|
|
public final static int CF_STATUS = 0x17;
|
|
|
|
public final static int CF_COMMAND = 0x17;
|
|
|
|
public final static int CF_LBA0 = 0x13;
|
|
|
|
public final static int CF_LBA1 = 0x14;
|
|
|
|
public final static int CF_LBA2 = 0x15;
|
|
|
|
public final static int CF_LBA3 = 0x16;
|
|
|
|
|
2020-08-08 10:14:43 +00:00
|
|
|
public final static byte CF_READ_SEC = 0x20;
|
|
|
|
public final static byte CF_WRITE_SEC = 0x30;
|
|
|
|
public final static byte CF_IDENTIFY = (byte) 0xEC;
|
2019-01-26 07:53:09 +00:00
|
|
|
|
2020-08-10 08:05:45 +00:00
|
|
|
boolean rom_paged;
|
2019-01-26 07:53:09 +00:00
|
|
|
byte[] rom;
|
|
|
|
byte[] ram;
|
|
|
|
|
|
|
|
byte cfCommand;
|
|
|
|
byte[] cfLBA = new byte[4];
|
2020-06-09 09:07:56 +00:00
|
|
|
byte cfSecCount;
|
2019-01-26 07:53:09 +00:00
|
|
|
File cfFile;
|
|
|
|
RandomAccessFile cf;
|
2020-08-08 10:14:43 +00:00
|
|
|
byte[] cfIdentifyBuffer = new byte[512];
|
|
|
|
int cfDataCount;
|
2019-01-26 07:53:09 +00:00
|
|
|
|
|
|
|
Z80 proc;
|
|
|
|
Thread thread;
|
2020-06-08 13:25:23 +00:00
|
|
|
long clockPeriodNs;
|
|
|
|
long clockTimeNs;
|
|
|
|
|
|
|
|
int tmsRam;
|
|
|
|
int tmsReg;
|
|
|
|
TMS9918 tms9918;
|
2019-01-26 07:53:09 +00:00
|
|
|
|
|
|
|
public Machine() {
|
|
|
|
rom = new byte[16384];
|
|
|
|
ram = new byte[65536];
|
|
|
|
|
2020-06-08 13:25:23 +00:00
|
|
|
clockPeriodNs = (long) (1000.0 / 7.3728);
|
|
|
|
clockTimeNs = 0;
|
2019-01-26 07:53:09 +00:00
|
|
|
|
|
|
|
proc = new Z80(this, null);
|
|
|
|
|
2020-06-08 13:25:23 +00:00
|
|
|
tms9918 = new TMS9918() {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onVSync() {
|
|
|
|
Machine.this.onTMS9918VSync();
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
tmsRam = 0x40;
|
|
|
|
tmsReg = 0x41;
|
|
|
|
|
2019-01-26 07:53:09 +00:00
|
|
|
thread = new Thread(new Runnable() {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
Machine.this.run();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setRom(int address, File file) throws IOException {
|
|
|
|
InputStream is = new FileInputStream(file);
|
|
|
|
is.read(rom, address, rom.length - address);
|
|
|
|
is.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setRom(int address, byte[] rom) throws IOException {
|
|
|
|
System.arraycopy(rom, 0, this.rom, address, Math.min(this.rom.length - address, rom.length));
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setClock(double freq) {
|
2020-06-08 13:25:23 +00:00
|
|
|
clockPeriodNs = (long) (1000.0 / freq);
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void setCompactFlash(File file) {
|
|
|
|
this.cfFile = file;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void start() {
|
|
|
|
try {
|
2020-08-08 10:14:43 +00:00
|
|
|
System.arraycopy("EM-CF-00000001 ".getBytes(), 0, cfIdentifyBuffer, 20, 20); // Serial number
|
|
|
|
System.arraycopy(Utility.getSwappedBytes("1.00 "), 0, cfIdentifyBuffer, 46, 8); // Firmware version
|
|
|
|
System.arraycopy(Utility.getSwappedBytes("EMULATED CF CARD "), 0, cfIdentifyBuffer, 54, 40); // Card model
|
|
|
|
|
2020-08-13 06:02:10 +00:00
|
|
|
if (cfFile != null && cfFile.exists()) {
|
2019-01-26 07:53:09 +00:00
|
|
|
cf = new RandomAccessFile(cfFile, "rw");
|
2020-08-08 10:14:43 +00:00
|
|
|
|
2020-08-10 08:05:45 +00:00
|
|
|
long size = cf.length() >> 9;
|
2020-08-08 10:14:43 +00:00
|
|
|
cfIdentifyBuffer[14] = (byte) (size >> 16);
|
|
|
|
cfIdentifyBuffer[15] = (byte) (size >> 24);
|
|
|
|
cfIdentifyBuffer[16] = (byte) (size);
|
|
|
|
cfIdentifyBuffer[17] = (byte) (size >> 8);
|
|
|
|
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
thread.start();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void run() {
|
|
|
|
long ns = System.nanoTime();
|
|
|
|
|
|
|
|
while (!Thread.interrupted()) {
|
|
|
|
synchronized (proc) {
|
2020-06-08 13:25:23 +00:00
|
|
|
int runTstates = (int) ((System.nanoTime() - ns) / clockPeriodNs);
|
2019-05-01 07:53:37 +00:00
|
|
|
if (runTstates >= 4) {
|
|
|
|
long prevTstates = tstates;
|
2020-06-08 13:25:23 +00:00
|
|
|
long prevClockTime = clockTimeNs;
|
2019-05-01 07:53:37 +00:00
|
|
|
while (tstates < (prevTstates + runTstates)) {
|
|
|
|
proc.execute();
|
|
|
|
}
|
2020-06-08 13:25:23 +00:00
|
|
|
long elapsed = clockTimeNs - prevClockTime;
|
|
|
|
tms9918.processFrame(elapsed);
|
|
|
|
onElapsedTime(elapsed);
|
|
|
|
ns += elapsed;
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2019-05-01 07:53:37 +00:00
|
|
|
Thread.sleep(1L);
|
2019-01-26 07:53:09 +00:00
|
|
|
} catch (InterruptedException e) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-08 13:25:23 +00:00
|
|
|
protected void onElapsedTime(long elapsedNs) {
|
|
|
|
// Do nothing
|
|
|
|
}
|
|
|
|
|
2019-01-26 07:53:09 +00:00
|
|
|
@Override
|
|
|
|
public void reset() {
|
|
|
|
synchronized (proc) {
|
2020-08-10 08:05:45 +00:00
|
|
|
rom_paged = true;
|
2019-01-26 07:53:09 +00:00
|
|
|
tstates = 0;
|
2020-06-08 13:25:23 +00:00
|
|
|
clockTimeNs = 0;
|
|
|
|
if (tms9918 != null) {
|
|
|
|
tms9918.reset();
|
|
|
|
}
|
2019-01-26 07:53:09 +00:00
|
|
|
proc.reset();
|
|
|
|
super.reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int fetchOpcode(int address) {
|
|
|
|
tstates += 4; // 3 clocks to fetch opcode from RAM and 1 execution clock
|
2020-06-08 13:25:23 +00:00
|
|
|
clockTimeNs += clockPeriodNs * 4;
|
2020-08-10 08:05:45 +00:00
|
|
|
address &= 0xFFFF;
|
|
|
|
if (rom_paged && address < rom.length) {
|
|
|
|
return rom[address] & 0xff;
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
2020-08-10 08:05:45 +00:00
|
|
|
return ram[address] & 0xff;
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int inPort(int port) {
|
|
|
|
tstates += 4; // 4 clocks for read byte from bus
|
2020-06-08 13:25:23 +00:00
|
|
|
clockTimeNs += clockPeriodNs * 4;
|
|
|
|
|
2020-08-13 06:02:10 +00:00
|
|
|
port &= 0xFF;
|
|
|
|
|
|
|
|
if (port == tmsRam) {
|
2020-06-08 13:25:23 +00:00
|
|
|
return tms9918.inRam();
|
|
|
|
}
|
2020-08-13 06:02:10 +00:00
|
|
|
if (port == tmsReg) {
|
2020-06-08 13:25:23 +00:00
|
|
|
return tms9918.inReg();
|
|
|
|
}
|
2019-01-26 07:53:09 +00:00
|
|
|
|
2020-08-13 06:02:10 +00:00
|
|
|
if (cf != null) {
|
|
|
|
switch (port) {
|
|
|
|
case CF_DATA:
|
|
|
|
if (cfCommand == CF_READ_SEC) {
|
|
|
|
if (cfDataCount < 512 * cfSecCount) {
|
|
|
|
cfDataCount++;
|
|
|
|
try {
|
|
|
|
if (cf != null) {
|
|
|
|
return (byte) cf.read();
|
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
2020-08-08 10:14:43 +00:00
|
|
|
}
|
2020-08-13 06:02:10 +00:00
|
|
|
return 0x00;
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-13 06:02:10 +00:00
|
|
|
else if (cfCommand == CF_IDENTIFY) {
|
|
|
|
if (cfDataCount < cfIdentifyBuffer.length) {
|
|
|
|
return cfIdentifyBuffer[cfDataCount++];
|
|
|
|
}
|
|
|
|
return 0x00;
|
2020-08-08 10:14:43 +00:00
|
|
|
}
|
2020-08-13 06:02:10 +00:00
|
|
|
return 0x00;
|
|
|
|
case CF_SECCOUNT:
|
|
|
|
return cfSecCount & 0xFF;
|
|
|
|
case CF_STATUS:
|
|
|
|
if (cfCommand == CF_WRITE_SEC || cfCommand == CF_READ_SEC) {
|
2020-08-08 10:14:43 +00:00
|
|
|
return 0x48; // CF Ready, DRQ
|
|
|
|
}
|
2020-08-13 06:02:10 +00:00
|
|
|
else if (cfCommand == CF_IDENTIFY) {
|
|
|
|
if (cfDataCount < cfIdentifyBuffer.length) {
|
|
|
|
return 0x48; // CF Ready, DRQ
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0x40; // CF Ready
|
|
|
|
case CF_ERROR:
|
|
|
|
return 0x01; // No error
|
|
|
|
case CF_SECTOR:
|
|
|
|
case CF_CYL_LOW:
|
|
|
|
case CF_CYL_HI:
|
|
|
|
case CF_HEAD:
|
|
|
|
return 0x00;
|
|
|
|
}
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
|
|
|
|
2020-08-13 06:02:10 +00:00
|
|
|
return port;
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void outPort(int port, int value) {
|
|
|
|
tstates += 4; // 4 clocks for write byte to bus
|
2020-06-08 13:25:23 +00:00
|
|
|
clockTimeNs += clockPeriodNs * 4;
|
|
|
|
|
2020-08-13 06:02:10 +00:00
|
|
|
port &= 0xFF;
|
|
|
|
value &= 0xFF;
|
|
|
|
|
|
|
|
if (port == tmsRam) {
|
2020-06-08 13:25:23 +00:00
|
|
|
tms9918.outRam(value);
|
|
|
|
}
|
2020-08-13 06:02:10 +00:00
|
|
|
if (port == tmsReg) {
|
2020-06-08 13:25:23 +00:00
|
|
|
tms9918.outReg(value);
|
|
|
|
}
|
2019-01-26 07:53:09 +00:00
|
|
|
|
2020-08-13 06:02:10 +00:00
|
|
|
if (cf != null) {
|
|
|
|
switch (port) {
|
|
|
|
case CF_DATA:
|
|
|
|
if (cfCommand == CF_WRITE_SEC) {
|
|
|
|
if (cfDataCount < 512 * cfSecCount) {
|
|
|
|
cfDataCount++;
|
|
|
|
try {
|
|
|
|
if (cf != null) {
|
|
|
|
cf.write(value & 0xFF);
|
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case CF_COMMAND:
|
|
|
|
cfCommand = (byte) value;
|
|
|
|
if (cfCommand == CF_WRITE_SEC || cfCommand == CF_READ_SEC) {
|
2020-08-08 10:14:43 +00:00
|
|
|
try {
|
2020-08-13 06:02:10 +00:00
|
|
|
long addr = ((cfLBA[3] & 0x0F) << 24) | ((cfLBA[2] & 0xFF) << 16) | ((cfLBA[1] & 0xFF) << 8) | (cfLBA[0] & 0xFF);
|
2020-08-08 10:14:43 +00:00
|
|
|
if (cf != null) {
|
2020-08-13 06:02:10 +00:00
|
|
|
cf.seek(addr << 9);
|
2020-08-08 10:14:43 +00:00
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
2020-08-13 06:02:10 +00:00
|
|
|
cfDataCount = 0;
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
2020-08-13 06:02:10 +00:00
|
|
|
else if (cfCommand == CF_IDENTIFY) {
|
|
|
|
cfDataCount = 0;
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
2020-08-13 06:02:10 +00:00
|
|
|
break;
|
|
|
|
case CF_LBA0:
|
|
|
|
cfLBA[0] = (byte) value;
|
|
|
|
break;
|
|
|
|
case CF_LBA1:
|
|
|
|
cfLBA[1] = (byte) value;
|
|
|
|
break;
|
|
|
|
case CF_LBA2:
|
|
|
|
cfLBA[2] = (byte) value;
|
|
|
|
break;
|
|
|
|
case CF_LBA3: {
|
|
|
|
cfLBA[3] = (byte) value;
|
|
|
|
break;
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
2020-08-13 06:02:10 +00:00
|
|
|
case CF_SECCOUNT:
|
|
|
|
cfSecCount = (byte) value;
|
|
|
|
break;
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
2020-08-13 06:02:10 +00:00
|
|
|
}
|
2020-08-10 08:05:45 +00:00
|
|
|
|
2020-08-13 06:02:10 +00:00
|
|
|
switch (port) {
|
2019-01-26 07:53:09 +00:00
|
|
|
case 0x38: // ROM page
|
2020-08-13 06:02:10 +00:00
|
|
|
rom_paged = value != 0x01;
|
2019-01-26 07:53:09 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int peek8(int address) {
|
|
|
|
tstates += 3; // 3 clocks for read byte from RAM
|
2020-06-08 13:25:23 +00:00
|
|
|
clockTimeNs += clockPeriodNs * 3;
|
2020-08-10 08:05:45 +00:00
|
|
|
address &= 0xFFFF;
|
|
|
|
if (rom_paged && address < rom.length) {
|
|
|
|
return rom[address] & 0xff;
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
2020-08-10 08:05:45 +00:00
|
|
|
return ram[address] & 0xff;
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void poke8(int address, int value) {
|
|
|
|
tstates += 3; // 3 clocks for write byte to RAM
|
2020-06-08 13:25:23 +00:00
|
|
|
clockTimeNs += clockPeriodNs * 3;
|
2020-08-10 08:05:45 +00:00
|
|
|
address &= 0xFFFF;
|
|
|
|
if (!rom_paged || address >= rom.length) {
|
|
|
|
ram[address] = (byte) value;
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public long getTstates() {
|
|
|
|
return tstates;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void stop() {
|
|
|
|
try {
|
|
|
|
thread.interrupt();
|
|
|
|
} catch (Exception e) {
|
|
|
|
// Do nothing
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
if (cf != null) {
|
|
|
|
cf.close();
|
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-08 13:25:23 +00:00
|
|
|
protected void onTMS9918VSync() {
|
|
|
|
// Do nothing
|
|
|
|
}
|
|
|
|
|
2019-01-26 07:53:09 +00:00
|
|
|
}
|