MOCKINGBOARD WORKS AGAIN!

This commit is contained in:
Brendan Robert 2024-02-15 11:34:16 -06:00
parent 3cd8a2d276
commit 856a660fde
6 changed files with 143 additions and 267 deletions

View File

@ -100,7 +100,6 @@ public abstract class Card extends Device {
@Override @Override
public void reconfigure() { public void reconfigure() {
//super.reconfigure();
// Emulator.whileSuspended(c-> { // Emulator.whileSuspended(c-> {
unregisterListeners(); unregisterListeners();
registerListeners(); registerListeners();

View File

@ -129,7 +129,9 @@ public abstract class Device implements Reconfigurable {
private void __doTickIsRunning() { private void __doTickIsRunning() {
for (Device d : childrenArray) { for (Device d : childrenArray) {
d.doTick(); if (d.isRunning() && !d.isPaused()) {
d.doTick();
}
} }
if (waitCycles <= 0) { if (waitCycles <= 0) {
tick(); tick();

View File

@ -43,9 +43,9 @@ public abstract class RAM implements Reconfigurable {
public PagedMemory activeRead; public PagedMemory activeRead;
public PagedMemory activeWrite; public PagedMemory activeWrite;
private Set<RAMListener> listeners; private final Set<RAMListener> listeners;
private Set<RAMListener>[] listenerMap; private final Set<RAMListener>[] listenerMap;
private Set<RAMListener>[] ioListenerMap; private final Set<RAMListener>[] ioListenerMap;
public Optional<Card>[] cards; public Optional<Card>[] cards;
// card 0 = 80 column card firmware / system rom // card 0 = 80 column card firmware / system rom
public int activeSlot = 0; public int activeSlot = 0;
@ -58,6 +58,8 @@ public abstract class RAM implements Reconfigurable {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public RAM() { public RAM() {
listeners = new ConcurrentSkipListSet<>(); listeners = new ConcurrentSkipListSet<>();
listenerMap = (Set<RAMListener>[]) new Set[256];
ioListenerMap = (Set<RAMListener>[]) new Set[256];
cards = (Optional<Card>[]) new Optional[8]; cards = (Optional<Card>[]) new Optional[8];
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
cards[i] = Optional.empty(); cards[i] = Optional.empty();
@ -202,10 +204,12 @@ public abstract class RAM implements Reconfigurable {
} }
} }
@SuppressWarnings("unchecked")
private void refreshListenerMap() { private void refreshListenerMap() {
listenerMap = (Set<RAMListener>[]) new Set[256]; // Wipe out existing maps
ioListenerMap = (Set<RAMListener>[]) new Set[256]; for (int i = 0; i < 256; i++) {
listenerMap[i] = null;
ioListenerMap[i] = null;
}
listeners.forEach(this::addListenerRange); listeners.forEach(this::addListenerRange);
} }

View File

@ -19,11 +19,6 @@
package jace.hardware; package jace.hardware;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -31,8 +26,6 @@ import jace.Emulator;
import jace.config.ConfigurableField; import jace.config.ConfigurableField;
import jace.config.Name; import jace.config.Name;
import jace.core.Card; import jace.core.Card;
import jace.core.Computer;
import jace.core.Motherboard;
import jace.core.RAMEvent; import jace.core.RAMEvent;
import jace.core.RAMEvent.TYPE; import jace.core.RAMEvent.TYPE;
import jace.core.RAMListener; import jace.core.RAMListener;
@ -50,7 +43,7 @@ import jace.hardware.mockingboard.R6522;
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com * @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/ */
@Name("Mockingboard") @Name("Mockingboard")
public class CardMockingboard extends Card implements Runnable { public class CardMockingboard extends Card {
// If true, emulation will cover 4 AY chips. Otherwise, only 2 AY chips // If true, emulation will cover 4 AY chips. Otherwise, only 2 AY chips
@ConfigurableField(name = "Volume", shortName = "vol", @ConfigurableField(name = "Volume", shortName = "vol",
@ -67,7 +60,6 @@ public class CardMockingboard extends Card implements Runnable {
defaultValue = "1020484", defaultValue = "1020484",
description = "Clock rate of AY oscillators") description = "Clock rate of AY oscillators")
public int CLOCK_SPEED = 1020484; public int CLOCK_SPEED = 1020484;
public int SAMPLE_RATE = 48000;
@ConfigurableField(name = "Buffer size", @ConfigurableField(name = "Buffer size",
category = "Sound", category = "Sound",
description = "Number of samples to generate on each pass") description = "Number of samples to generate on each pass")
@ -76,13 +68,14 @@ public class CardMockingboard extends Card implements Runnable {
public PSG[] chips; public PSG[] chips;
// The 6522 controllr chips (always 2) // The 6522 controllr chips (always 2)
public R6522[] controllers; public R6522[] controllers;
static private int ticksBetweenPlayback = 200;
Lock timerSync = new ReentrantLock();
Condition cpuCountReached = timerSync.newCondition();
Condition playbackFinished = timerSync.newCondition();
@ConfigurableField(name = "Idle sample threshold", description = "Number of samples to wait before suspending sound") @ConfigurableField(name = "Idle sample threshold", description = "Number of samples to wait before suspending sound")
private final int MAX_IDLE_SAMPLES = SAMPLE_RATE; int[] left, right;
SoundBuffer buffer;
int ticksBetweenPlayback = 24;
int MAX_IDLE_TICKS = 1000000;
boolean activatedAfterReset = false;
boolean debug = false;
@Override @Override
public String getDeviceName() { public String getDeviceName() {
return "Mockingboard"; return "Mockingboard";
@ -90,13 +83,14 @@ public class CardMockingboard extends Card implements Runnable {
public CardMockingboard() { public CardMockingboard() {
super(); super();
activatedAfterReset = false;
left = new int[BUFFER_LENGTH];
right = new int[BUFFER_LENGTH];
controllers = new R6522[2]; controllers = new R6522[2];
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
// has to be final to be used inside of anonymous class below // has to be final to be used inside of anonymous class below
final int j = i; final int j = i;
controllers[i] = new R6522() { controllers[i] = new R6522() {
final int controller = j;
@Override @Override
public void sendOutputA(int value) { public void sendOutputA(int value) {
chips[j].setBus(value); chips[j].setBus(value);
@ -132,39 +126,25 @@ public class CardMockingboard extends Card implements Runnable {
@Override @Override
public String getShortName() { public String getShortName() {
return "timer" + j; return "timer" + j;
} }
public void tick() {
super.tick();
if (controller == 0) {
doSoundTick();
}
}
}; };
addChildDevice(controllers[i]);
} }
} }
@Override @Override
public void reset() { public void reset() {
activatedAfterReset = false;
suspend(); suspend();
} }
RAMListener mainListener = null; RAMListener mainListener = null;
boolean heatbeatUnclocked = false;
long heartbeatReclockTime = 0L;
long unclockTime = 5000L;
private void setUnclocked(boolean unclocked) {
heatbeatUnclocked = unclocked;
for (R6522 controller : controllers) {
controller.setUnclocked(unclocked);
}
heartbeatReclockTime = System.currentTimeMillis() + unclockTime;
}
@Override @Override
protected void handleFirmwareAccess(int register, TYPE type, int value, RAMEvent e) { protected void handleFirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
resume(); if (chips == null) {
reconfigure();
}
int chip = 0; int chip = 0;
for (PSG psg : chips) { for (PSG psg : chips) {
if (psg.getBaseReg() == (register & 0x0f0)) { if (psg.getBaseReg() == (register & 0x0f0)) {
@ -181,12 +161,19 @@ public class CardMockingboard extends Card implements Runnable {
if (e.getType().isRead()) { if (e.getType().isRead()) {
int val = controller.readRegister(register & 0x0f); int val = controller.readRegister(register & 0x0f);
e.setNewValue(val); e.setNewValue(val);
// System.out.println("Read "+Integer.toHexString(register)+" == "+val); if (debug) System.out.println("Chip " + chip + " Read "+Integer.toHexString(register & 0x0f)+" == "+val);
} else { } else {
controller.writeRegister(register & 0x0f, e.getNewValue()); controller.writeRegister(register & 0x0f, e.getNewValue());
// System.out.println("Write "+Integer.toHexString(register)+" == "+e.getNewValue()); if (debug) System.out.println("Chip " + chip + " Write "+Integer.toHexString(register & 0x0f)+" == "+e.getNewValue());
} }
} // Any firmware access will reset the idle counter and wake up the card, this allows the timers to start running again
// Games such as "Skyfox" use the timer to detect if the card is present.
idleTicks = 0;
if (!isRunning() || isPaused()) {
activatedAfterReset = true;
resume();
}
}
@Override @Override
protected void handleIOAccess(int register, TYPE type, int value, RAMEvent e) { protected void handleIOAccess(int register, TYPE type, int value, RAMEvent e) {
@ -195,75 +182,66 @@ public class CardMockingboard extends Card implements Runnable {
Emulator.withVideo(v->e.setNewValue(v.getFloatingBus())); Emulator.withVideo(v->e.setNewValue(v.getFloatingBus()));
} }
long ticksSinceLastPlayback = 0; long ticksSinceLastPlayback = 0;
long idleTicks = 0;
@Override @Override
public void tick() { public void tick() {
if (heatbeatUnclocked) { try {
if (System.currentTimeMillis() - heartbeatReclockTime >= unclockTime) { ticksSinceLastPlayback++;
setUnclocked(false); if (ticksSinceLastPlayback >= ticksBetweenPlayback) {
} else { ticksSinceLastPlayback -= ticksBetweenPlayback;
for (R6522 c : controllers) { if (playSound(left, right)) {
if (c == null || !c.isRunning()) { idleTicks = 0;
continue; } else {
} idleTicks += ticksBetweenPlayback;
c.doTick();
} }
} }
} catch (InterruptedException | ExecutionException | SoundError | NullPointerException ex) {
Logger.getLogger(CardMockingboard.class.getName()).log(Level.SEVERE, "Mockingboard playback encountered fatal exception", ex);
suspend();
// Do nothing, probably suspending CPU
} }
}
if (idleTicks >= MAX_IDLE_TICKS) {
public boolean isRunning() { suspend();
return super.isRunning() && playbackThread != null && playbackThread.isAlive();
}
private void doSoundTick() {
if (isRunning() && !pause) {
// buildMixerTable();
timerSync.lock();
try {
ticksSinceLastPlayback++;
if (ticksSinceLastPlayback >= ticksBetweenPlayback) {
cpuCountReached.signalAll();
while (isRunning() && ticksSinceLastPlayback >= ticksBetweenPlayback) {
if (!playbackFinished.await(1, TimeUnit.SECONDS)) {
// gripe("The mockingboard playback thread has stalled. Disabling mockingboard.");
suspendSound();
}
}
}
} catch (InterruptedException ex) {
suspend();
// Do nothing, probably suspending CPU
} finally {
timerSync.unlock();
}
} }
} }
@Override @Override
public void reconfigure() { public void reconfigure() {
boolean restart = suspend();
initPSG(); initPSG();
for (PSG chip : chips) { for (PSG chip : chips) {
chip.setRate(phasorMode ? CLOCK_SPEED * 2 : CLOCK_SPEED, SAMPLE_RATE); chip.setRate(phasorMode ? CLOCK_SPEED * 2 : CLOCK_SPEED, SoundMixer.RATE);
chip.reset(); chip.reset();
} }
long motherboardSpeed = Emulator.withComputer(c->c.getMotherboard().getSpeedInHz(), 1L);
ticksBetweenPlayback = (int) ((motherboardSpeed * BUFFER_LENGTH) / SoundMixer.RATE);
buildMixerTable();
super.reconfigure(); super.reconfigure();
if (restart) {
resume();
}
} }
/////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////
public static int[] VolTable; public static int[] VolTable;
public void playSound(int[] left, int[] right) { public boolean playSound(int[] left, int[] right) throws InterruptedException, ExecutionException, SoundError {
if (buffer == null) {
return false;
}
chips[0].update(left, true, left, false, left, false, BUFFER_LENGTH); chips[0].update(left, true, left, false, left, false, BUFFER_LENGTH);
chips[1].update(right, true, right, false, right, false, BUFFER_LENGTH); chips[1].update(right, true, right, false, right, false, BUFFER_LENGTH);
if (phasorMode) { if (phasorMode) {
chips[2].update(left, false, left, false, left, false, BUFFER_LENGTH); chips[2].update(left, false, left, false, left, false, BUFFER_LENGTH);
chips[3].update(right, false, right, false, right, false, BUFFER_LENGTH); chips[3].update(right, false, right, false, right, false, BUFFER_LENGTH);
} }
boolean nonZeroSamples = false;
for (int i=0; i < BUFFER_LENGTH; i++) {
buffer.playSample((short) left[i]);
buffer.playSample((short) right[i]);
if (left[i] != 0 || right[i] != 0) {
nonZeroSamples = true;
}
}
return nonZeroSamples;
} }
public void buildMixerTable() { public void buildMixerTable() {
@ -288,176 +266,61 @@ public class CardMockingboard extends Card implements Runnable {
VolTable[0] = 0; VolTable[0] = 0;
} }
Thread playbackThread = null;
boolean pause = false;
@Override @Override
public void resume() { public void resume() {
pause = false; if (!activatedAfterReset) {
// Do not re-activate until firmware access was made
return;
}
if (buffer == null || !buffer.isAlive()) {
try {
buffer = SoundMixer.createBuffer(true);
} catch (InterruptedException | ExecutionException | SoundError e) {
System.out.println("Error whhen trying to create sound buffer for Mockingboard: " + e.getMessage());
e.printStackTrace();
suspend();
}
}
if (chips == null) { if (chips == null) {
initPSG(); initPSG();
for (PSG psg : chips) { for (PSG psg : chips) {
psg.setRate(phasorMode ? CLOCK_SPEED * 2 : CLOCK_SPEED, SAMPLE_RATE); psg.setRate(phasorMode ? CLOCK_SPEED * 2 : CLOCK_SPEED, SoundMixer.RATE);
psg.reset(); psg.reset();
} }
} }
if (!isRunning()) { idleTicks = 0;
setUnclocked(true); setPaused(false);
for (R6522 controller : controllers) { reconfigure();
controller.attach();
controller.resume();
}
}
super.resume(); super.resume();
if (playbackThread == null || !playbackThread.isAlive()) {
playbackThread = new Thread(this, "Mockingboard sound playback");
playbackThread.start();
}
} }
@Override @Override
public boolean suspend() { public boolean suspend() {
super.suspend(); if (buffer != null) {
for (R6522 controller : controllers) { try {
controller.suspend(); buffer.shutdown();
controller.detach(); } catch (InterruptedException | ExecutionException | SoundError e) {
System.out.println("Error when trying to shutdown sound buffer for Mockingboard: " + e.getMessage());
e.printStackTrace();
} finally {
buffer = null;
}
} }
return suspendSound(); return super.suspend();
} }
public boolean suspendSound() {
setRun(false);
if (playbackThread == null || !playbackThread.isAlive()) {
return false;
}
if (playbackThread != null) {
try {
playbackThread.join(500);
} catch (InterruptedException ex) {
}
}
playbackThread = null;
return true;
}
@Override
/**
* This is the audio playback thread
*/
public void run() {
SoundBuffer buffer;
try {
buffer = SoundMixer.createBuffer(true);
} catch (InterruptedException | ExecutionException | SoundError e) {
e.printStackTrace();
setRun(false);
return;
}
try {
if (buffer == null) {
setRun(false);
return;
}
System.out.println("Mockingboard playback started");
int bufferSize = SoundMixer.BUFFER_SIZE;
int[] left = new int[bufferSize];
int[] right = new int[bufferSize];
buildMixerTable();
ticksBetweenPlayback = (int) ((Motherboard.DEFAULT_SPEED * BUFFER_LENGTH) / SAMPLE_RATE);
System.out.println("Ticks between playback: "+ticksBetweenPlayback);
ticksSinceLastPlayback = 0;
int zeroSamples = 0;
setRun(true);
LockSupport.parkNanos(5000);
while (isRunning() && !Thread.interrupted()) {
while (isRunning() && !Emulator.withComputer(Computer::isRunning, false)) {
Thread.sleep(1000);
}
if (isRunning() && !Thread.interrupted()) {
playSound(left, right);
try {
for (int i=0; i < bufferSize; i++) {
buffer.playSample((short) left[i]);
buffer.playSample((short) right[i]);
}
timerSync.lock();
ticksSinceLastPlayback -= ticksBetweenPlayback;
} catch (ExecutionException | SoundError e) {
Logger.getLogger(CardMockingboard.class.getName()).log(Level.SEVERE, "Mockingboard playback encountered fatal exception", e);
try {
buffer.shutdown();
} catch (ExecutionException | SoundError e1) {
// Ignore shutdown errors, we're already reporting a fatal error
}
buffer=null;
setRun(false);
break;
} finally {
timerSync.unlock();
}
if (zeroSamples >= MAX_IDLE_SAMPLES) {
zeroSamples = 0;
pause = true;
Emulator.withComputer(c->c.getMotherboard().cancelSpeedRequest(this));
while (pause && isRunning()) {
try {
Thread.sleep(50);
timerSync.lock();
playbackFinished.signalAll();
} catch (InterruptedException ex) {
return;
} catch (IllegalMonitorStateException ex) {
// Do nothing
} finally {
try {
timerSync.unlock();
} catch (IllegalMonitorStateException ex) {
// Do nothing -- this is probably caused by a suspension event
}
}
}
}
try {
timerSync.lock();
playbackFinished.signalAll();
while (isRunning() && ticksSinceLastPlayback < ticksBetweenPlayback) {
Emulator.withComputer(c->c.getMotherboard().requestSpeed(this));
cpuCountReached.await();
Emulator.withComputer(c->c.getMotherboard().cancelSpeedRequest(this));
}
} catch (InterruptedException ex) {
// Do nothing, probably killing playback thread on purpose
} finally {
timerSync.unlock();
}
}
}
} catch (InterruptedException ex) {
Logger.getLogger(CardMockingboard.class.getName()).log(Level.SEVERE, null, ex);
} finally {
Emulator.withComputer(c->c.getMotherboard().cancelSpeedRequest(this));
System.out.println("Mockingboard playback stopped");
if (buffer != null && buffer.isAlive()) {
try {
buffer.shutdown();
} catch (InterruptedException | ExecutionException | SoundError e) {
// Ignore errors during shutdown
}
}
}
}
private void initPSG() { private void initPSG() {
if (phasorMode) { if (phasorMode) {
chips = new PSG[4]; chips = new PSG[4];
chips[0] = new PSG(0x10, CLOCK_SPEED * 2, SAMPLE_RATE, "AY1", 8); chips[0] = new PSG(0x10, CLOCK_SPEED * 2, SoundMixer.RATE, "AY1", 8);
chips[1] = new PSG(0x80, CLOCK_SPEED * 2, SAMPLE_RATE, "AY2", 8); chips[1] = new PSG(0x80, CLOCK_SPEED * 2, SoundMixer.RATE, "AY2", 8);
chips[2] = new PSG(0x10, CLOCK_SPEED * 2, SAMPLE_RATE, "AY3", 16); chips[2] = new PSG(0x10, CLOCK_SPEED * 2, SoundMixer.RATE, "AY3", 16);
chips[3] = new PSG(0x80, CLOCK_SPEED * 2, SAMPLE_RATE, "AY4", 16); chips[3] = new PSG(0x80, CLOCK_SPEED * 2, SoundMixer.RATE, "AY4", 16);
} else { } else {
chips = new PSG[2]; chips = new PSG[2];
chips[0] = new PSG(0, CLOCK_SPEED, SAMPLE_RATE, "AY1", 255); chips[0] = new PSG(0, CLOCK_SPEED, SoundMixer.RATE, "AY1", 255);
chips[1] = new PSG(0x80, CLOCK_SPEED, SAMPLE_RATE, "AY2", 255); chips[1] = new PSG(0x80, CLOCK_SPEED, SoundMixer.RATE, "AY2", 255);
} }
} }
@ -467,8 +330,8 @@ public class CardMockingboard extends Card implements Runnable {
} }
// This fixes freezes when resizing the window, etc. // This fixes freezes when resizing the window, etc.
@Override // @Override
public boolean suspendWithCPU() { // public boolean suspendWithCPU() {
return true; // return true;
} // }
} }

View File

@ -33,7 +33,7 @@ import java.util.Map;
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com * @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/ */
public class PSG { public class PSG {
boolean debug = false;
int baseReg; int baseReg;
/* register ids */ /* register ids */
@ -129,22 +129,22 @@ public class PSG {
public void setControl(int c) { public void setControl(int c) {
BusControl cmd = BusControl.fromInt(c); BusControl cmd = BusControl.fromInt(c);
if (cmd == null) { if (cmd == null) {
// System.out.println("Bad control param "+c); if (debug) System.out.println("Bad control param "+c);
return; return;
} }
switch (cmd) { switch (cmd) {
case inactive: case inactive:
break; break;
case latch: case latch:
// System.out.println("PSG latched register "+selectedReg); if (debug) System.out.println("PSG latched register "+selectedReg);
selectedReg = bus & 0x0f; selectedReg = bus & 0x0f;
break; break;
case read: case read:
bus = getReg(Reg.get(selectedReg)); bus = getReg(Reg.get(selectedReg));
// System.out.println("PSG read register "+selectedReg + " == "+bus); if (debug) System.out.println("PSG read register "+selectedReg + " == "+bus);
break; break;
case write: case write:
// System.out.println("PSG wrote register "+selectedReg + " == "+bus); if (debug) System.out.println("PSG wrote register "+selectedReg + " == "+bus);
setReg(Reg.get(selectedReg), bus); setReg(Reg.get(selectedReg), bus);
break; break;
} }

View File

@ -19,14 +19,14 @@
package jace.hardware.mockingboard; package jace.hardware.mockingboard;
import jace.Emulator; import jace.Emulator;
import jace.core.TimedDevice; import jace.core.Device;
/** /**
* Implementation of 6522 VIA chip * Implementation of 6522 VIA chip
* *
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com * @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/ */
public abstract class R6522 extends TimedDevice { public abstract class R6522 extends Device {
public static long SPEED = 1020484L; // (NTSC) public static long SPEED = 1020484L; // (NTSC)
public R6522() { public R6522() {
@ -35,14 +35,14 @@ public abstract class R6522 extends TimedDevice {
timer1running = true; timer1running = true;
timer1latch = 0x1fff; timer1latch = 0x1fff;
timer1interruptEnabled = false; timer1interruptEnabled = false;
setSpeedInHz(SPEED); // setSpeedInHz(SPEED);
setRun(true); // setRun(true);
} }
@Override // @Override
public long defaultCyclesPerSecond() { // public long defaultCyclesPerSecond() {
return SPEED; // return SPEED;
} // }
// 6522 VIA // 6522 VIA
// http://www.applevault.com/twiki/Main/Mockingboard/6522.pdf // http://www.applevault.com/twiki/Main/Mockingboard/6522.pdf
@ -141,7 +141,8 @@ public abstract class R6522 extends TimedDevice {
public int timer2counter = 0; public int timer2counter = 0;
public boolean timer2running = false; public boolean timer2running = false;
public boolean unclocked = false; public boolean unclocked = false;
public boolean debug = false;
@Override @Override
protected String getDeviceName() { protected String getDeviceName() {
return "6522 VIA Chip"; return "6522 VIA Chip";
@ -149,16 +150,18 @@ public abstract class R6522 extends TimedDevice {
@Override @Override
public void tick() { public void tick() {
if (!unclocked) { // if (!unclocked) {
if (timer1running) { if (timer1running) {
timer1counter--; timer1counter--;
if (debug && timer1counter % 1000 == 0)
System.out.println(getShortName() + " Timer 1 counter: "+timer1counter+" Timer 1 interrupt enabled: "+timer1interruptEnabled);
if (timer1counter < 0) { if (timer1counter < 0) {
timer1counter = timer1latch; timer1counter = timer1latch;
if (!timer1freerun) { if (!timer1freerun) {
timer1running = false; timer1running = false;
} }
if (timer1interruptEnabled) { if (timer1interruptEnabled) {
// System.out.println("Timer 1 generated interrupt"); if (debug) System.out.println("Timer 1 generated interrupt");
timer1IRQ = true; timer1IRQ = true;
Emulator.withComputer(c->c.getCpu().generateInterrupt()); Emulator.withComputer(c->c.getCpu().generateInterrupt());
} }
@ -166,19 +169,23 @@ public abstract class R6522 extends TimedDevice {
} }
if (timer2running) { if (timer2running) {
timer2counter--; timer2counter--;
if (debug && timer2counter % 1000 == 0)
System.out.println(getShortName() + " Timer 2 counter: "+timer2counter+" Timer 2 interrupt enabled: "+timer2interruptEnabled);
if (timer2counter < 0) { if (timer2counter < 0) {
timer2running = false; timer2running = false;
timer2counter = timer2latch; timer2counter = timer2latch;
if (timer2interruptEnabled) { if (timer2interruptEnabled) {
if (debug) System.out.println("Timer 2 generated interrupt");
timer2IRQ = true; timer2IRQ = true;
Emulator.withComputer(c->c.getCpu().generateInterrupt()); Emulator.withComputer(c->c.getCpu().generateInterrupt());
} }
} }
} }
if (!timer1running && !timer2running) { if (!timer1running && !timer2running) {
setRun(false); if (debug) System.out.println("No timers active, suspending");
suspend();
} }
} // }
} }
public void setUnclocked(boolean unclocked) { public void setUnclocked(boolean unclocked) {
@ -198,7 +205,7 @@ public abstract class R6522 extends TimedDevice {
public void writeRegister(int reg, int val) { public void writeRegister(int reg, int val) {
int value = val & 0x0ff; int value = val & 0x0ff;
Register r = Register.fromInt(reg); Register r = Register.fromInt(reg);
// System.out.println("Writing "+(value&0x0ff)+" to register "+r.toString()); if (debug) System.out.println(getShortName() + " Writing "+Integer.toHexString(value&0x0ff)+" to register "+r.toString());
switch (r) { switch (r) {
case ORB: case ORB:
if (dataDirectionB == 0) { if (dataDirectionB == 0) {
@ -228,7 +235,6 @@ public abstract class R6522 extends TimedDevice {
timer1IRQ = false; timer1IRQ = false;
timer1counter = timer1latch; timer1counter = timer1latch;
timer1running = true; timer1running = true;
setRun(true);
break; break;
case T1LH: case T1LH:
timer1latch = (timer1latch & 0x0ff) | (value << 8); timer1latch = (timer1latch & 0x0ff) | (value << 8);
@ -242,7 +248,6 @@ public abstract class R6522 extends TimedDevice {
timer2IRQ = false; timer2IRQ = false;
timer2counter = timer2latch; timer2counter = timer2latch;
timer2running = true; timer2running = true;
setRun(true);
break; break;
case SR: case SR:
// SHIFT REGISTER NOT IMPLEMENTED // SHIFT REGISTER NOT IMPLEMENTED
@ -252,7 +257,6 @@ public abstract class R6522 extends TimedDevice {
timer1freerun = (value & 64) != 0; timer1freerun = (value & 64) != 0;
if (timer1freerun) { if (timer1freerun) {
timer1running = true; timer1running = true;
setRun(true);
} }
break; break;
case PCR: case PCR:
@ -277,6 +281,10 @@ public abstract class R6522 extends TimedDevice {
break; break;
default: default:
} }
if (timer1running || timer2running) {
if (debug) System.out.println("One or more timers active, resuming");
resume();
}
} }
// Whatever uses 6522 will want to know when it is outputting values // Whatever uses 6522 will want to know when it is outputting values
@ -287,7 +295,7 @@ public abstract class R6522 extends TimedDevice {
public int readRegister(int reg) { public int readRegister(int reg) {
Register r = Register.fromInt(reg); Register r = Register.fromInt(reg);
// System.out.println("Reading register "+r.toString()); if (debug) System.out.println(getShortName() + " Reading register "+r.toString());
switch (r) { switch (r) {
case ORB -> { case ORB -> {
if (dataDirectionB == 0x0ff) { if (dataDirectionB == 0x0ff) {