/**
* Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.codesourcery.jasm16.emulator.devices.impl;
import java.util.concurrent.atomic.AtomicBoolean;
import de.codesourcery.jasm16.Address;
import de.codesourcery.jasm16.Register;
import de.codesourcery.jasm16.emulator.EmulationListener;
import de.codesourcery.jasm16.emulator.ICPU;
import de.codesourcery.jasm16.emulator.IEmulationListener;
import de.codesourcery.jasm16.emulator.IEmulator;
import de.codesourcery.jasm16.emulator.ILogger;
import de.codesourcery.jasm16.emulator.devices.DeviceDescriptor;
import de.codesourcery.jasm16.emulator.devices.HardwareInterrupt;
import de.codesourcery.jasm16.emulator.devices.IDevice;
import de.codesourcery.jasm16.emulator.memory.IMemory;
import de.codesourcery.jasm16.utils.Misc;
public class DefaultClock implements IDevice
{
private static final int INITIAL_TICKS_PER_SECOND = (int) Math.round( 1000.0 / 60.0d);
private static final DeviceDescriptor DESC = new DeviceDescriptor("Generic clock",
"Generic clock (compatible)" , 0x12d0b402,1, Constants.JASM16_MANUFACTURER );
private volatile IEmulator emulator;
private final ClockThread clockThread = new ClockThread();
private volatile ILogger out;
private final AtomicBoolean emulationRunning = new AtomicBoolean(false);
private final IEmulationListener myEmulationListener = new EmulationListener() {
public boolean belongsToHardwareDevice() {
return true;
}
protected void beforeContinuousExecutionHook() {
emulationRunning.set( true );
}
public void onStopHook(IEmulator emulator, Address previousPC, Throwable emulationError) {
emulationRunning.set( false);
}
};
@Override
public void reset()
{
stopClock();
clockThread.sleepDelay = INITIAL_TICKS_PER_SECOND;
clockThread.irqEnabled = false;
clockThread.irqMessage = 0;
clockThread.tickCounter = 0;
}
protected final class ClockThread extends Thread {
private volatile boolean isRunnable = false;
private volatile int tickCounter = 0;
private volatile boolean terminate = false;
private volatile int sleepDelay=INITIAL_TICKS_PER_SECOND; // 60 HZ
private volatile boolean irqEnabled = false;
private volatile int irqMessage = 0;
private final Object SLEEP_LOCK = new Object();
public ClockThread()
{
setDaemon(true);
setName("hw-clock-thread");
}
@Override
public void run()
{
out.debug("Clock thread started.");
while ( ! terminate )
{
while( ! isRunnable )
{
if ( terminate ) {
break;
}
out.debug("Clock thread stopped.");
synchronized (SLEEP_LOCK)
{
try {
SLEEP_LOCK.wait();
if ( terminate ) {
break;
}
} catch (InterruptedException e) { }
}
}
if ( emulationRunning.get() )
{
tickCounter = ( tickCounter+1 ) % 0x10000;
if ( irqEnabled && emulator != null ) {
emulator.triggerInterrupt( new HardwareInterrupt( DefaultClock.this , irqMessage ) );
}
}
try {
Thread.sleep( sleepDelay );
} catch (InterruptedException e) {
}
}
out.debug("Default clock shutdown.");
}
public void terminate() {
terminate=true;
synchronized (SLEEP_LOCK) {
SLEEP_LOCK.notifyAll();
}
while ( clockThread.isAlive() )
{
out.debug("Waiting for clock thread to terminate...");
try {
Thread.sleep( 250 );
} catch (InterruptedException e) { }
}
}
public void setTicksPerSecond(int ticksPerSecond)
{
stopClock();
tickCounter = 0;
out.debug("Clock set to tick "+ticksPerSecond+" times per second");
sleepDelay = (int) Math.round( 1000.0 / ticksPerSecond );
}
public void startClock() {
if ( ! isRunnable ) {
isRunnable = true;
synchronized (SLEEP_LOCK)
{
SLEEP_LOCK.notifyAll();
}
}
}
public void stopClock() {
if ( isRunnable ) {
isRunnable = false;
}
}
}
protected synchronized void startClock()
{
if ( ! clockThread.isAlive() ) {
clockThread.start();
}
clockThread.startClock();
}
protected synchronized void stopClock()
{
if ( ! clockThread.isAlive() ) {
return;
}
clockThread.stopClock();
}
@Override
public void afterAddDevice(IEmulator emulator)
{
if ( this.emulator != null ) {
throw new IllegalStateException("Clock "+this+" already associated with emulator "+emulator+" ?");
}
this.emulator = emulator;
this.emulator.addEmulationListener( myEmulationListener );
this.out = emulator.getOutput();
}
@Override
public void beforeRemoveDevice(IEmulator emulator)
{
clockThread.terminate();
this.emulator.removeEmulationListener( myEmulationListener );
this.emulator = null;
}
@Override
public DeviceDescriptor getDeviceDescriptor()
{
return DESC;
}
@Override
public int handleInterrupt(IEmulator emulator, ICPU cpu, IMemory memory)
{
/*
* Name: Generic Clock (compatible)
* ID: 0x12d0b402
* Version: 1
*
* Interrupts do different things depending on contents of the A register:
*
* A | BEHAVIOR
* ---+----------------------------------------------------------------------------
* 0 | The B register is read, and the clock will tick 60/B times per second.
* | If B is 0, the clock is turned off.
* 1 | Store number of ticks elapsed since last call to 0 in C register
* 2 | If register B is non-zero, turn on interrupts with message B. If B is zero,
* | disable interrupts
* ---+----------------------------------------------------------------------------
*
* When interrupts are enabled, the clock will trigger an interrupt whenever it
* ticks.
*/
final int a = cpu.getRegisterValue(Register.A);
switch( a )
{
case 0: // The B register is read, and the clock will tick 60/B times per second. If B is 0, the clock is turned off.
int b = cpu.getRegisterValue(Register.B) & 0xffff;
if ( b == 0 ) {
stopClock();
return 0;
}
if ( b < 0 ) {
clockThread.setTicksPerSecond( 60 );
} else if ( b > 60 ) {
clockThread.setTicksPerSecond( 1 );
} else {
clockThread.setTicksPerSecond( (int) Math.round( 60.0 / b ) );
}
startClock();
break;
case 1:
// Store number of ticks elapsed since last call to 0 in C register
cpu.setRegisterValue( Register.C , clockThread.tickCounter & 0xffff );
break;
case 2:
// If register B is non-zero, turn on interrupts with message B. If B is zero, disable interrupts.
b = cpu.getRegisterValue(Register.B) & 0xffff;
if ( b == 0 ) {
clockThread.irqEnabled=false;
} else {
clockThread.irqMessage = b;
clockThread.irqEnabled = true;
out.debug("Clock IRQs enabled with message "+Misc.toHexString( b ));
}
break;
default:
out.warn("handleInterrupt(): Clock received unknown interrupt msg "+Misc.toHexString( a ));
}
return 0;
}
@Override
public boolean supportsMultipleInstances() {
return false;
}
@Override
public String toString() {
return "'"+DESC.getDescription()+"'";
}
}