/** * This file is a part of JaC64 - a Java C64 Emulator * Main Developer: Joakim Eriksson (Dreamfabric.com) * Contact: joakime@sics.se * Web: http://www.dreamfabric.com/c64 * --------------------------------------------------- * This is the CPU file for Commodore 64 with its * ROM files and memory management, etc. * * @(#)cpu.java Created date: 99-5-17 * */ package com.dreamfabric.jac64; import com.dreamfabric.c64utils.*; /** * CPU "implements" the C64s 6510 processor in java code. reimplemented from old * CPU.java * * @author Joakim Eriksson (joakime@sics.se) * @version $Revision:$, $Date:$ */ public class CPU extends MOS6510Core { public static final boolean DEBUG_EVENT = false; // The IO RAM memory at 0x10000 (just since there is RAM there...) public static final int IO_OFFSET = 0x10000 - 0xd000; public static final int BASIC_ROM2 = 0x1a000; public static final int KERNAL_ROM2 = 0x1e000; public static final int CHAR_ROM2 = 0x1d000; public static final boolean EMULATE_1541 = true; public static final int CH_PROTECT = 1; public static final int CH_MONITOR_WRITE = 2; public static final int CH_MONITOR_READ = 4; private int romFlag = 0xa000; // Defaults for the ROMs public boolean basicROM = true; public boolean kernalROM = true; public boolean charROM = false; public boolean ioON = true; // The state of the program (runs if running = true) public boolean running = true; public boolean pause = false; private static final long CYCLES_PER_DEBUG = 10000000; public static final boolean DEBUG = false; private C1541Emu c1541; private Loader loader; private int windex = 0; private int cheatMon[]; private AutoStore[] autoStore; public CPU(IMonitor m, String cb, Loader loader) { super(m, cb); memory = new int[0x20000]; this.loader = loader; if (EMULATE_1541) { IMonitor d = new DefaultIMon(); // new Debugger(); c1541 = new C1541Emu(d, cb); // d.init(c1541); // d.setEnabled(true); } } public C1541Emu getDrive() { return c1541; } private final void schedule(long cycles) { chips.clock(cycles); while (cycles >= scheduler.nextTime) { TimeEvent t = scheduler.popFirst(); if (t != null) { if (DEBUG_EVENT) { System.out.println("Executing event: " + t.getShort()); } // Give it the actual time also!!! t.execute(cycles); } else { if (DEBUG_EVENT) System.out.println("Nothign to execute..."); return; } } } // Reads the memory with all respect to all flags... protected final int fetchByte(int adr) { /* a cycles passes for this read */ cycles++; /* Chips work first, then CPU */ schedule(cycles); while (baLowUntil > cycles) { cycles++; schedule(cycles); } if ((romFlag & adr) == romFlag) { return memory[rindex = adr | 0x10000]; } else if ((adr & 0xf000) == 0xd000) { if (ioON) { return chips.performRead(rindex = adr, cycles); } else if (charROM) { return memory[rindex = adr | 0x10000]; } else { return memory[rindex = adr]; } } else { return memory[rindex = adr]; } } // A byte is written directly to memory or to ioChips protected final void writeByte(int adr, int data) { cycles++; schedule(cycles); // Locking only on fetch byte... // System.out.println("Writing byte at: " + Integer.toString(adr, 16) // + " = " + data); if (adr <= 1) { memory[adr] = data; int p = (memory[0] ^ 0xff) | memory[1]; kernalROM = ((p & 2) == 2); // Kernal on basicROM = ((p & 3) == 3); // Basic on charROM = ((p & 3) != 0) && ((p & 4) == 0); // ioON is probably not correct!!! Check against table... ioON = ((p & 3) != 0) && ((p & 4) != 0); if (basicROM) romFlag = 0xa000; else if (kernalROM) romFlag = 0xe000; else romFlag = 0x10000; // No Rom at all (Basic / Kernal) } adr &= 0xffff; if (ioON && ((adr & 0xf000) == 0xd000)) { // System.out.println("IO Write at: " + Integer.toString(adr, 16)); chips.performWrite(adr, data, cycles); } else { memory[windex = adr] = data; } } private void fixRindex(int adr) { // ROM/RAM address fix if ((basicROM && ((adr & 0xe000) == 0xa000)) || (kernalROM && ((adr & 0xe000) == 0xe000)) || (charROM && ((adr & 0xf000) == 0xd000))) { // Add ROM address for the read! adr |= 0x10000; } rindex = adr; } public void poke(int address, int data) { writeByte(address & 0xffff, data & 0xff); } public void patchROM(PatchListener list) { this.list = list; int pos = 0xf49e | 0x10000; memory[pos++] = M6510Ops.JSR; memory[pos++] = 0xd2; memory[pos++] = 0xf5; System.out.println("Patched LOAD at: " + Hex.hex2(pos)); memory[pos++] = LOAD_FILE; memory[pos++] = M6510Ops.RTS; } public void runBasic() { memory[631] = (int) 'R'; memory[632] = (int) 'U'; memory[633] = (int) 'N'; memory[634] = 13;// enter memory[198] = 4; // length } public void enterText(String txt) { System.out.println("Entering text into textbuffer: " + txt); txt = txt.toUpperCase(); int len = txt.length(); int pos = 0; for (int i = 0, n = len; i < n; i++) { char c = txt.charAt(i); if (c == '~') c = 13; memory[631 + pos] = c; pos++; if (pos == 5) { memory[198] = pos; pos = 0; int tries = 5; while (tries > 0 && memory[198] > 0) { try { Thread.sleep(50); } catch (Exception e) { e.printStackTrace(); } tries--; if (tries == 0) { System.out.println("Buffer still full: " + memory[198]); } } } } memory[198] = pos; int tries = 5; while (tries > 0 && memory[198] > 0) { try { Thread.sleep(50); } catch (Exception e) { e.printStackTrace(); } tries--; if (tries == 0) { System.out.println("Buffer still full: " + memory[198]); } } } protected void installROMS() { loadROM(loader.getResourceStream("/roms/kernal.c64"), KERNAL_ROM2, 0x2000); loadROM(loader.getResourceStream("/roms/basic.c64"), BASIC_ROM2, 0x2000); loadROM(loader.getResourceStream("/roms/chargen.c64"), CHAR_ROM2, 0x1000); } public void run(int address) { reset(); running = true; setPC(address); loop(); } public void unknownInstruction(int pc, int op) { switch (op) { case SLEEP: cycles += 100; break; case LOAD_FILE: if (acc == 0) monitor.info("**** LOAD FILE! ***** PC = " + Integer.toString(pc, 16) + " => " + Integer.toString(rindex, 16)); else monitor.info("**** VERIFY! ***** PC = " + pc + " => " + rindex); int len; int mptr = memory[0xbb] + (memory[0xbc] << 8); monitor.info("Filename len:" + (len = memory[0xb7])); String name = ""; for (int i = 0; i < len; i++) name += (char) memory[mptr++]; name += '\n'; int sec = memory[0xb9]; monitor.info("name = " + name); monitor.info("Sec Address: " + sec); int loadAdr = -1; if (sec == 0) loadAdr = memory[0x2b] + (memory[0x2c] << 8); if (list != null) { if (list.readFile(name, loadAdr)) { acc = 0; } } pc--; break; } } // Takes the thread and loops!!! public void start() { run(0xfce2); // Power UP reset routine! if (pause) { while (pause) { System.out.println("Entering pause mode..."); synchronized(this) { try { wait(); } catch (Exception e) { } } System.out.println("Exiting pause mode..."); loop(); } } } // Should pause the application! public synchronized void setPause(boolean p) { if (p) { pause = true; running = false; } else { pause = false; running = true; } notify(); } public synchronized void stop() { // stop completely running = false; pause = false; notify(); } public void reset() { writeByte(1, 0x7); super.reset(); if (EMULATE_1541) { c1541.reset(); } } public void setPC(int startAdress) { // The processor flags pc = startAdress; } public String getName() { return "C64 CPU"; } /** * The main emulation <code>loop</code>. * * @param startAdress * an <code>int</code> value that represent the starting * address of the emulator */ public void loop() { if (cheatMon != null) { cheatLoop(); return; } long next_print = cycles + CYCLES_PER_DEBUG; // How much should this be??? monitor.info("Starting CPU at: " + Integer.toHexString(pc)); try { while (running) { // Debugging? if (monitor.isEnabled()) { // || interruptInExec > 0) { if (baLowUntil <= cycles) { fixRindex(pc); // sets the rindex! monitor.disAssemble(memory, rindex, acc, x, y, (byte) getStatusByte(), interruptInExec, lastInterrupt); } } // Run one instruction! emulateOp(); // Also allow the 1541 to run an instruction! if (EMULATE_1541) { c1541.tick(cycles); } nr_ins++; if (next_print < cycles) { long sec = System.currentTimeMillis() - lastMillis; int level = monitor.getLevel(); if (DEBUG && level > 1) { monitor.info("--------------------------"); monitor.info("Nr ins:" + nr_ins + " sec:" + (sec) + " -> " + ((nr_ins * 1000) / sec) + " ins/s" + " " + " clk: " + cycles + " clk/s: " + ((CYCLES_PER_DEBUG * 1000) / sec) + "\n" + ((nr_irq * 1000) / sec)); if (level > 2) monitor.disAssemble(memory, rindex, acc, x, y, (byte) getStatusByte(), interruptInExec, lastInterrupt); monitor.info("--------------------------"); } nr_irq = 0; nr_ins = 0; lastMillis = System.currentTimeMillis(); next_print = cycles + CYCLES_PER_DEBUG; } } } catch (Exception e) { monitor.error("Exception in loop " + pc + " : " + e); e.printStackTrace(); monitor.disAssemble(memory, rindex, acc, x, y, (byte) getStatusByte(), interruptInExec, lastInterrupt); } } // ------------------------------------------------------------------- // Cheat loop! // Protection // + rule triggered auto get/store // Rule: xpr & xpr & xpr ... // rule: int[] adr, cmptype, cmpval ... // autostore: int[] adr, len => result in hex! from adr and on! // ------------------------------------------------------------------- public void setAutoStore(int index, AutoStore au) { autoStore[index] = au; } public AutoStore getAutoStore(int index) { return autoStore[index]; } public void setCheatEnabled(int maxAutostores) { cheatMon = new int[0x10000]; autoStore = new AutoStore[maxAutostores]; } public void protect(int address, int value) { cheatMon[address] = (cheatMon[address] & 0xff) | (value << 8) | CH_PROTECT; } public void monitorRead(int address) { cheatMon[address] |= CH_MONITOR_READ; } public void monitorWrite(int address) { cheatMon[address] |= CH_MONITOR_WRITE; } public void cheatLoop() { int t; try { while (running) { // Run one instruction! emulateOp(); // Processor read from address... if (rindex < 0x10000) { if ((t = cheatMon[rindex]) != 0) { if ((t & CH_MONITOR_READ) != 0) { for (int i = 0, n = autoStore.length; i < n; i++) { if (autoStore[i] != null) autoStore[i].checkRules(memory); } } } } if (windex < 0x10000) { if ((t = cheatMon[windex]) != 0) { if ((t & CH_PROTECT) != 0) { // Write back value from then protected... memory[windex] = (cheatMon[windex] >> 16) & 0xff; } if ((t & CH_MONITOR_WRITE) != 0) { for (int i = 0, n = autoStore.length; i < n; i++) { if (autoStore[i] != null) autoStore[i].checkRules(memory); } } } } // Also allow the 1541 to run an instruction! if (EMULATE_1541) { c1541.tick(cycles); } } } catch (Exception e) { monitor.error("Exception in loop " + pc + " : " + e); e.printStackTrace(); monitor.disAssemble(memory, rindex, acc, x, y, (byte) getStatusByte(), interruptInExec, lastInterrupt); } } }