/** * 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; import java.util.ArrayList; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import org.apache.log4j.Logger; import de.codesourcery.jasm16.Address; import de.codesourcery.jasm16.AddressRange; import de.codesourcery.jasm16.Register; import de.codesourcery.jasm16.Size; import de.codesourcery.jasm16.WordAddress; import de.codesourcery.jasm16.ast.OperandNode.OperandPosition; import de.codesourcery.jasm16.disassembler.DisassembledLine; import de.codesourcery.jasm16.disassembler.Disassembler; import de.codesourcery.jasm16.emulator.devices.DeviceDescriptor; import de.codesourcery.jasm16.emulator.devices.IDevice; import de.codesourcery.jasm16.emulator.devices.IInterrupt; import de.codesourcery.jasm16.emulator.devices.SoftwareInterrupt; import de.codesourcery.jasm16.emulator.exceptions.DeviceErrorException; import de.codesourcery.jasm16.emulator.exceptions.EmulationErrorException; import de.codesourcery.jasm16.emulator.exceptions.InterruptQueueFullException; import de.codesourcery.jasm16.emulator.exceptions.InvalidDeviceSlotNumberException; import de.codesourcery.jasm16.emulator.exceptions.InvalidTargetOperandException; import de.codesourcery.jasm16.emulator.exceptions.UnknownOpcodeException; import de.codesourcery.jasm16.emulator.memory.IMemoryRegion; import de.codesourcery.jasm16.emulator.memory.IReadOnlyMemory; import de.codesourcery.jasm16.emulator.memory.MainMemory; import de.codesourcery.jasm16.emulator.memory.MemUtils; import de.codesourcery.jasm16.utils.Misc; /** * DCPU-16 emulator. * * @author tobias.gierke@code-sourcery.de */ public final class Emulator implements IEmulator { private static final Logger LOG = Logger.getLogger(Emulator.class); private static final boolean DEBUG = false; /** * Maximum number of interrupts the emulator's interrupt queue may hold. */ public static final int INTERRUPT_QUEUE_SIZE = 256; private static final boolean DEBUG_LISTENER_PERFORMANCE = false; private final IdentityHashMap<IEmulationListener,Long> listenerPerformance = new IdentityHashMap<IEmulationListener,Long>(); private final ClockThread clockThread; private final ListenerHelper listenerHelper = new ListenerHelper(); private static final AtomicLong cmdId = new AtomicLong(0); /** * Worker thread command types. * * @author tobias.gierke@code-sourcery.de */ protected enum CommandType { START, STOP, TERMINATE, SPEED_CHANGE; } /** * Command to control the emulation's worker thread responsible * for the actual DCPU-16 instruction execution. * * <p>Command senders may request an acknowledge message * from the worker thread and block until it is received.Each * command carries a unique ID so that an * acknowledge message can be matched with the corresponding command.</p> * * @author tobias.gierke@code-sourcery.de */ protected static final class Command { private final long id = cmdId.incrementAndGet(); private final Object payload; private final CommandType type; private final boolean requiresACK; public static Command terminateClockThread() { return new Command(CommandType.TERMINATE , true ); } public static Command stopCommandWithoutACK() { return new Command(CommandType.STOP,false); } public static Command stopCommand() { return new Command(CommandType.STOP,true); } public static Command startCommand() { return new Command(CommandType.START, true); } public static Command changeSpeedCommand(EmulationSpeed newSpeed) { return new Command(CommandType.SPEED_CHANGE,true,newSpeed); } protected Command(CommandType type, boolean requiresACK) { this(type,requiresACK,null); } protected Command(CommandType type, boolean requiresACK,Object payload) { this.type = type; this.payload = payload; this.requiresACK = requiresACK; } public Object getPayload() { return payload; } public boolean hasType(CommandType t) { return t.equals( this.type ); } public boolean requiresACK() { return requiresACK; } public boolean isTerminateCommand() { return type == CommandType.TERMINATE; } public boolean isStopCommand() { return type == CommandType.STOP|| type == CommandType.TERMINATE; } /** * Check whether this is a command that should make the * worker thread enter it's main <code>while()</code> loop. * * <p>When this method returns <code>true</code> , it does <b>not</b> mean * that the worker thread will actually start executing instructions , it will * only wake it up. * </p> * @return */ public boolean isStartWorkerMainLoopCommand() { return ! isStopCommand() || isTerminateCommand(); } public long getId() { return id; } @Override public String toString() { return type+" ( "+id+" , requires_ack="+requiresACK+" )"; } } /** * Helper to manage IEmulationListeners. * This class manages multiple internal lists , each for a specific listener type. * That way we avoid having to look-up listeners with a specific type each * time a notification needs to be send. * * @author tobias.gierke@code-sourcery.de */ protected final class ListenerHelper { // @GuardedBy( emuListeners ) private final List<IEmulationListener> emuListeners = new ArrayList<IEmulationListener>(); // @GuardedBy( emuListeners ) private final List<IEmulationListener> beforeCommandExecListeners = new ArrayList<IEmulationListener>(); // @GuardedBy( emuListeners ) private final List<IEmulationListener> continuousModeBeforeCommandExecListeners = new ArrayList<IEmulationListener>(); // @GuardedBy( emuListeners ) private final List<IEmulationListener> afterCommandExecListeners = new ArrayList<IEmulationListener>(); // @GuardedBy( emuListeners ) private final List<IEmulationListener> continuousModeAfterCommandExecListeners = new ArrayList<IEmulationListener>(); private final IEmulationListenerInvoker BEFORE_COMMAND_INVOKER = new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { listener.beforeCommandExecution( emulator ); } }; public void addEmulationListener(IEmulationListener listener) { if (listener == null) { throw new IllegalArgumentException("listener must not be NULL."); } synchronized (emuListeners) { emuListeners.add( listener ); if ( listener.isInvokeBeforeCommandExecution() ) { beforeCommandExecListeners.add( listener ); if ( listener.isInvokeAfterAndBeforeCommandExecutionInContinuousMode() ) { continuousModeBeforeCommandExecListeners.add( listener ); } } if ( listener.isInvokeAfterCommandExecution() ) { afterCommandExecListeners.add( listener ); if ( listener.isInvokeAfterAndBeforeCommandExecutionInContinuousMode() ) { continuousModeAfterCommandExecListeners.add( listener ); } } } } public void removeAllEmulationListeners() { synchronized (emuListeners) { removeAllNonHardwareListeners( emuListeners ); removeAllNonHardwareListeners( beforeCommandExecListeners ); removeAllNonHardwareListeners( continuousModeBeforeCommandExecListeners ); removeAllNonHardwareListeners( continuousModeAfterCommandExecListeners ); removeAllNonHardwareListeners( afterCommandExecListeners ); } } private void removeAllNonHardwareListeners(List<IEmulationListener> list) { for ( Iterator<IEmulationListener> it = list.iterator() ; it.hasNext() ; ) { if ( ! it.next().belongsToHardwareDevice() ) { it.remove(); } } } public void removeEmulationListener(IEmulationListener listener) { if (listener == null) { throw new IllegalArgumentException("listener must not be NULL."); } synchronized (emuListeners) { emuListeners.remove( listener ); beforeCommandExecListeners.remove( listener ); continuousModeBeforeCommandExecListeners.remove( listener ); continuousModeAfterCommandExecListeners.remove( listener ); afterCommandExecListeners.remove( listener ); } } public void notifyListeners(IEmulationListenerInvoker invoker) { notifyListeners( invoker , emuListeners ); } public void invokeAfterCommandExecutionListeners(boolean continousMode,final int executedCommandDuration) { final IEmulationListenerInvoker invoker = new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { listener.afterCommandExecution( emulator , executedCommandDuration ); } }; if ( continousMode ) { notifyListeners( invoker , continuousModeAfterCommandExecListeners ); } else { notifyListeners( invoker , afterCommandExecListeners ); } } public void invokeBeforeCommandExecutionListeners(boolean continousMode) { if ( continousMode ) { notifyListeners( BEFORE_COMMAND_INVOKER , continuousModeBeforeCommandExecListeners ); } else { notifyListeners( BEFORE_COMMAND_INVOKER , beforeCommandExecListeners ); } } public void notifyListeners(IEmulationListenerInvoker invoker,List<IEmulationListener> listeners) { final List<IEmulationListener> copy; synchronized( emuListeners ) { if ( listeners.isEmpty() ) { return; } copy = new ArrayList<IEmulationListener>( listeners ); } if ( DEBUG_LISTENER_PERFORMANCE ) { for ( IEmulationListener l : copy) { long execTime = -System.currentTimeMillis(); try { invoker.invoke( Emulator.this , l ); } catch(Exception e) { LOG.error("notifyListeners(): Listener "+l+" failed",e); } finally { execTime += System.currentTimeMillis(); } final Long existing = listenerPerformance.get( l ); if ( existing == null ) { listenerPerformance.put( l , execTime ); } else { listenerPerformance.put( l , existing.longValue() + execTime ); } } } else { final int len = copy.size(); for ( int i = 0 ; i < len ; i++) { final IEmulationListener l = copy.get(i); try { invoker.invoke( Emulator.this , l ); } catch(Exception e) { LOG.error("notifyListeners(): Listener "+l+" failed",e); } } } } public void emulatorDisposed() { notifyListeners( new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { listener.beforeEmulatorIsDisposed( emulator ); } }); final List<IEmulationListener> copy; synchronized( emuListeners ) { copy = new ArrayList<IEmulationListener>( emuListeners ); } for ( IEmulationListener l : copy ) { removeEmulationListener( l ); } } } private volatile boolean ignoreAccessToUnknownDevices=false; private volatile boolean checkMemoryWrites = false; private volatile boolean haltOnStoreToImmediateValue = true; private volatile Throwable lastEmulationError = null; private volatile EmulationSpeed emulationSpeed = EmulationSpeed.MAX_SPEED; // ============ BreakPoints ======= // @GuardedBy( breakpoints ) private final Map<Address,List<Breakpoint>> breakpoints = new HashMap<Address,List<Breakpoint>>(); // ============ Memory ============ // memory needs to be thread-safe since the emulation runs in a separate thread // and UI threads may access the registers concurrently private final MainMemory memory = new MainMemory( 65536 , checkMemoryWrites ); // ========= devices =========== // @GuardedBy( devices ) private final List<IDevice> devices = new ArrayList<IDevice>(); // ============ CPU =============== private final Object CPU_LOCK = new Object(); private Address lastValidInstruction = null; // @GuardedBy( CPU_LOCK ) private final CPU cpu = new CPU(memory); // @GuardedBy( CPU_LOCK ) private final CPU visibleCPU = new CPU(memory); // a,b,c,x,y,z,i,j // all CPU registers needs to be thread-safe since the emulation runs in a separate thread // and UI threads may access the registers concurrently private volatile ILogger loggerDelegate = new PrintStreamLogger( System.out ); private final ILogger out = new ILogger() { @Override public void setLogLevel(de.codesourcery.jasm16.emulator.ILogger.LogLevel logLevel) { loggerDelegate.setLogLevel( logLevel ); } @Override public boolean isDebugEnabled() { return loggerDelegate.isDebugEnabled(); } @Override public void info(String message) { loggerDelegate.info(message); } @Override public void info(String message, Throwable cause) { loggerDelegate.info(message, cause); } @Override public void warn(String message) { loggerDelegate.warn(message); } @Override public void warn(String message, Throwable cause) { loggerDelegate.warn(message, cause); } @Override public void error(String message) { loggerDelegate.error(message); } @Override public void error(String message, Throwable cause) { loggerDelegate.error(message, cause); } @Override public void debug(String message) { loggerDelegate.debug(message); } @Override public void debug(String message, Throwable cause) { loggerDelegate.debug(message, cause); } }; /* (non-Javadoc) * @see de.codesourcery.jasm16.emulator.IEmulator#reset() */ @Override public void reset(boolean clearMemory) { stop(null); // reset memory BEFORE resetting devices // because if a device uses MMIO, doing the // resetMemory() call after resetDevices() may // trigger the device again and thus pollute // the reset state resetMemory(clearMemory); resetDevices(); synchronized( CPU_LOCK ) { cpu.reset(); visibleCPU.reset(); } // notify listeners listenerHelper.notifyListeners( new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { listener.afterReset( emulator ); } }); } private void resetMemory(boolean clearMemory) { memory.resetWriteProtection(); if ( clearMemory ) { memory.clear(); } } private void resetDevices() { for ( IDevice device : getDevices() ) { try { device.reset(); } catch(Exception e) { LOG.error("reset(): Device "+device+" failed during reset",e); } } } @Override public boolean stop() { return stop( null ); } protected boolean stop(final Throwable cause) { this.lastEmulationError = cause; final boolean emulationWasRunning = clockThread.stopSimulation(); if ( emulationWasRunning ) { listenerHelper.notifyListeners( new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { final Address pc; synchronized( CPU_LOCK ) { pc = visibleCPU.pc; } listener.onStop( emulator , pc , cause ); } }); } // remove all internal breakpoints final List<Breakpoint> internalBPs = new ArrayList<Breakpoint>(); synchronized( breakpoints ) { for ( List<Breakpoint> bps : breakpoints.values() ) { for ( Breakpoint bp : bps ) { if ( bp.isOneShotBreakpoint() ) { internalBPs.add( bp ); } } } } for ( Breakpoint bp : internalBPs ) { deleteBreakpoint( bp ); } return emulationWasRunning; } @Override public void start() { listenerHelper.notifyListeners( new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { listener.beforeContinuousExecution( emulator ); } }); clockThread.startSimulation(); } public Emulator() { clockThread = new ClockThread(); clockThread.start(); } /** * Thread responsible for execution of the * actual instruction emulation. * * <p>This thread's main loop supports two * execution modes, either {@link EmulationSpeed#MAX_SPEED} * or {@link EmulationSpeed#REAL_SPEED}.</p> * * @author tobias.gierke@code-sourcery.de */ public final class ClockThread extends Thread { // values used for emulation speed calculations private long lastStart = 0; private int cycleCountAtLastStart=0; private long lastStop = 0; private int cycleCountAtLastStop=0; private final AtomicBoolean isRunnable = new AtomicBoolean(false); private final BlockingQueue<Command> cmdQueue = new ArrayBlockingQueue<Command>(1); private final BlockingQueue<Long> ackQueue = new ArrayBlockingQueue<Long>(300); // execution delay loop parameters determined by calibrate() method private volatile double adjustmentFactor = 1.0d; private volatile int oneCycleDelay = -1; // just a dummy value used in our delay loop private int dummy; // the current emulation speed private EmulationSpeed currentSpeed = emulationSpeed; public ClockThread() { setName("emulation-clock-thread"); setDaemon(true); } @Override public void run() { lastEmulationError = null; Command cmd = waitForStartCommand(); if ( cmd.isTerminateCommand() ) { out.info("Emulator thread terminated."); acknowledgeCommand( cmd ); return; } lastStart = System.currentTimeMillis(); cycleCountAtLastStart=cpu.currentCycle; acknowledgeCommand( cmd ); while ( true ) { if ( isRunnable.get() == false ) { // halt execution lastStop = System.currentTimeMillis(); cycleCountAtLastStop = cpu.currentCycle; if ( DEBUG_LISTENER_PERFORMANCE ) { for ( Map.Entry<IEmulationListener,Long> entry : listenerPerformance.entrySet() ) { out.debug( entry.getKey()+" = "+entry.getValue()+" millis" ); } } out.info("Emulator stopped."); out.info("Executed cycles: "+(cycleCountAtLastStop-cycleCountAtLastStart) +" ( in "+getRuntimeInSeconds()+" seconds )"); out.info("Estimated clock rate: "+getEstimatedClockSpeed() ); cmd = waitForStopCommand(); if ( cmd.isTerminateCommand() ) { acknowledgeCommand( cmd ); break; } acknowledgeCommand( cmd ); cmd = waitForStartCommand(); if ( cmd.isTerminateCommand() ) { acknowledgeCommand( cmd ); break; } lastEmulationError = null; cycleCountAtLastStart = cpu.currentCycle; lastStart = System.currentTimeMillis(); acknowledgeCommand(cmd); } /* ================ * Execute ONE instruction * ================ */ final int durationInCycles = internalExecuteOneInstruction(); if ( currentSpeed == EmulationSpeed.REAL_SPEED ) { // adjust execution speed every 10000 cycles // to account for CPU load changes / JIT / different instruction profiles if ( ( cpu.currentCycle % 10000 ) == 0 ) { final double deltaSeconds = ( System.currentTimeMillis() - lastStart) / 1000d; final double cyclesPerSecond = (cpu.currentCycle-cycleCountAtLastStart) / deltaSeconds; if ( ! Double.isInfinite( cyclesPerSecond ) ) { adjustmentFactor = ( cyclesPerSecond / 100000.0d ); } } // delay execution, this code is exactly the same code as the one timed in // measureDelayLoop() int j = (int) (oneCycleDelay*durationInCycles*adjustmentFactor); for ( ; j > 0 ; j-- ) { dummy = ((dummy*2+j*2)/3)/(dummy*7+j); } } } out.info("Emulator thread terminated."); } public void startSimulation() { if ( isRunnable.compareAndSet( false , true ) ) { sendToClockThread( Command.startCommand() ); } } /** * Stop simulation. * * @return true if the simulation was currently running and has been stopped, false if the simulation was already stopped. */ public boolean stopSimulation() { if ( isRunnable.compareAndSet(true,false) ) { if ( Thread.currentThread() == clockThread ) { sendToClockThread( Command.stopCommandWithoutACK() ); // no point in sending an ACK since the clock thread itself triggered the stop() } else { sendToClockThread( Command.stopCommand() ); } return true; } return false; } public void changeSpeed(EmulationSpeed newSpeed) { sendToClockThread( Command.changeSpeedCommand( newSpeed ) ); } private final AtomicBoolean terminateCommandReceived = new AtomicBoolean(false); private void sendToClockThread(Command cmd) { if ( cmd.hasType(CommandType.TERMINATE ) ) { if ( ! terminateCommandReceived.compareAndSet( false , true ) ) { throw new IllegalStateException("Can't process any more commands , worker thread already terminated"); } } safePut( cmdQueue , cmd ); if ( ! cmd.requiresACK() ) { // don't wait , we'll never receive this one anyway return; } do { final Long cmdId = ackQueue.peek(); if ( cmdId != null && cmdId.longValue() == cmd.getId() ) { safeTake( ackQueue ); return; } try { java.lang.Thread.sleep(100); } catch(InterruptedException e) { Thread.currentThread().interrupt(); } } while ( true ); } private <T> T safeTake(BlockingQueue<T> queue) { while(true) { try { return queue.take(); } catch (InterruptedException e) { Thread.currentThread(); } } } public double getRuntimeInSeconds() { if ( lastStart != 0 && lastStop != 0 ) { final long delta = lastStop - lastStart; if ( delta >= 0) { return delta / 1000.0d; } LOG.error("getRuntimeInSeconds(): Negative runtime ? "+delta+" ( lastStart: "+lastStart+" / lastStop: "+lastStop,new Exception()); throw new RuntimeException("Unreachable code reached"); } else if ( lastStart != 0 ) { return ( (double) System.currentTimeMillis() - (double) lastStart) / 1000.0d; } else if ( lastStart == 0 && lastStop == 0 ) { return 0; } LOG.error("getRuntimeInSeconds(): Unreachable code reached"); throw new RuntimeException("Unreachable code reached"); } protected final double measureDelayLoopInNanos() { double averages = 0.0d; int count = 0; for ( int i = 0 ; i < 10 ; i++ ) { averages += measureDelayLoop(); count++; } return (averages / count); } protected final double measureDelayLoop() { final int oldValue = clockThread.oneCycleDelay; final int LOOP_COUNT = 1000000; oneCycleDelay = LOOP_COUNT; final long nanoStart = System.nanoTime(); for ( int j = oneCycleDelay ; j > 0 ; j-- ) { dummy = ((dummy*2+j*2)/3)/(dummy*7+j); } long durationNanos = ( System.nanoTime() - nanoStart ); if ( durationNanos < 0) { durationNanos = -durationNanos; } oneCycleDelay = oldValue; return ( (double) durationNanos / (double) LOOP_COUNT); } public String getEstimatedClockSpeed() { final double clockRate = getCyclesPerSecond(); final double delta = clockRate-100000.0; final double deviationPercentage = 100.0d*( delta / 100000.0 ); final String sign = deviationPercentage > 0 ? "+" : ""; final String deviation = " ( "+sign+deviationPercentage+" % )"; if ( clockRate == -1.0d ) { return "<cannot calculate clock rate>"; } if ( clockRate < 1000 ) { return clockRate+" Hz"+deviation; } else if ( clockRate < 100000) { return (clockRate/1000)+" kHz"+deviation; } else if ( clockRate < 1000000000 ) { return (clockRate/1000000)+" MHz"+deviation; } return (clockRate/1000000000)+" GHz"+deviation; } public double getCyclesPerSecond() { if ( lastStart != 0 ) { final double runtime = getRuntimeInSeconds(); if ( runtime != 0.0d ) { return ((double)cycleCountAtLastStop - (double) cycleCountAtLastStart ) / getRuntimeInSeconds(); } } return 0.0d; } private Command waitForStopCommand() { return waitForCommand(false); } private Command waitForStartCommand() { return waitForCommand(true); } private Command waitForCommand(boolean expectingStartCommand) { while ( true ) { final Command result = safeTake( cmdQueue ); // note that everything that is NOT a stop/terminate command // is considered to be a START command so we need to adjust // the speed here before checking Command#isStartCommand() if ( result.hasType( CommandType.SPEED_CHANGE ) ) { currentSpeed = (EmulationSpeed) result.getPayload(); out.info("Emulation speed changed changed to "+currentSpeed); acknowledgeCommand( result ); continue; } if ( ( expectingStartCommand && result.isStartWorkerMainLoopCommand() ) || ( ! expectingStartCommand && result.isStopCommand() ) ) { return result; } acknowledgeCommand( result ); } } private void acknowledgeCommand(Command cmd) { if ( cmd.requiresACK() ) { safePut( ackQueue , cmd.getId() ); } } } public <T> T doWithEmulator(IEmulatorInvoker<T> invoker) { synchronized( CPU_LOCK ) { return invoker.doWithEmulator( this , cpu , memory ); } } /** * * @return number of DCPU-16 cycles the command execution took */ protected int internalExecuteOneInstruction() { beforeCommandExecution(); int execDurationInCycles=-1; try { boolean success = false; synchronized( CPU_LOCK ) { try { execDurationInCycles = cpu.executeInstruction(); lastValidInstruction = visibleCPU.pc; if ( checkMemoryWrites ) { // note-to-self: I cannot simply do ( currentPC - previousPC ) here because // the instruction might've been a JSR or ADD PC, X / SUB PC,Y or // a jump into an interrupt handler that skipped over non-instruction memory final int sizeInWords = calculateInstructionSizeInWords( visibleCPU.pc.getWordAddressValue() , memory ); memory.writeProtect( new AddressRange( visibleCPU.pc , Size.words( sizeInWords )) ); } cpu.currentCycle+=execDurationInCycles; cpu.pc = Address.wordAddress( cpu.currentInstructionPtr ); cpu.maybeProcessOneInterrupt(); // might push A on stack and set PC to IA success = true; } finally { if ( success ) { visibleCPU.populateFrom( cpu ); } else { // restore CPU register state on error cpu.populateFrom(visibleCPU); } } } } catch(EmulationErrorException e) { stop( e ); LOG.error( "internalExecuteOneInstruction(): Emulation error - "+e.getMessage()); out.warn("Simulation stopped due to a program error ( at address ."+visibleCPU.pc+")",e); return 0; } catch(Exception e) { stop( e ); LOG.error( "internalExecuteOneInstruction(): Internal error - "+e.getMessage(),e); out.error("Simulation stopped due to an internal error ( at address ."+visibleCPU.pc+")"); return 0; } finally { afterCommandExecution( execDurationInCycles , cpu ); } return execDurationInCycles; } @Override public void executeOneInstruction() { stop(null); internalExecuteOneInstruction(); } protected void beforeCommandExecution() { listenerHelper.invokeBeforeCommandExecutionListeners( this.clockThread.isRunnable.get() ); } /** * * @param executedCommandDuration duration (in cycles) of last command or -1 if execution failed with an internal emulator error */ protected void afterCommandExecution(final int executedCommandDuration,CPU hiddenCPU) { // invoke listeners listenerHelper.invokeAfterCommandExecutionListeners( clockThread.isRunnable.get() , executedCommandDuration ); // check whether we reached a breakpoint maybeHandleBreakpoint(hiddenCPU); } private void maybeHandleBreakpoint(CPU hiddenCPU) { /* * We can have at most 2 breakpoints at any address, * one regular (user-defined) breakpoint and * one internal breakpoint used by stepReturn() */ Breakpoint regularBP = null; Breakpoint oneShotBP = null; synchronized( breakpoints ) { final List<Breakpoint> candidates = breakpoints.get( hiddenCPU.pc ); if ( candidates == null || candidates.isEmpty() ) { return; } for ( Breakpoint bp : candidates ) { if ( bp.matches( this ) ) { if ( bp.isOneShotBreakpoint() ) { if ( oneShotBP == null ) { oneShotBP = bp; } else { throw new RuntimeException("Internal error, more than one internal breakpoint at "+bp.getAddress()); } } else { if ( regularBP == null ) { regularBP = bp; } else { throw new RuntimeException("Internal error, more than one regular breakpoint at "+bp.getAddress()); } } } } } if ( regularBP != null || oneShotBP != null ) { // stop() will also remove ANY one-shot breakpoints from the list stop(null); if ( regularBP != null ) // only notify client code about the regular BP, internal BP is invisible to the user { final Breakpoint finalRegularBP = regularBP; // Closures can only access stuff declared final... listenerHelper.notifyListeners( new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { listener.onBreakpoint( emulator , finalRegularBP ); } }); } } } private boolean isConditionalInstruction(int instructionWord) { final int opCode = (instructionWord & 0x1f); return ( opCode >= 0x10 && opCode <= 0x17); } @Override public void skipCurrentInstruction() { synchronized(CPU_LOCK) { int adr = cpu.pc.getWordAddressValue(); adr += calculateInstructionSizeInWords( adr , memory ); cpu.pc = Address.wordAddress( adr ); visibleCPU.populateFrom( cpu ); } afterCommandExecution( 0 , visibleCPU ); } public static int calculateInstructionSizeInWords(Address address,IReadOnlyMemory memory) { return calculateInstructionSizeInWords(address.getWordAddressValue() , memory ); } public static int calculateInstructionSizeInWords(int address,IReadOnlyMemory memory) { @SuppressWarnings("deprecation") final int instructionWord = memory.read( address ); final int opCode = (instructionWord & 0x1f); int instructionSizeInWords=1; // +1 word for instruction itself switch( opCode ) { case 0x00: // skip special opcode instructionSizeInWords += getOperandsSizeInWordsForSpecialInstruction( instructionWord ); break; case 0x01: // SET case 0x02: // ADD case 0x03: // SUB case 0x04: // MUL case 0x05: // MLI case 0x06: // DIV case 0x07: // DVI case 0x08: // MOD case 0x09: // MDI case 0x0a: // AND case 0x0b: // BOR case 0x0c: // XOR case 0x0d: // SHR case 0x0e: // ASR case 0x0f: // SHL case 0x10: // IFB case 0x11: // IFC case 0x12: // IFE case 0x13: // IFN case 0x14: // IFG case 0x15: // IFA case 0x16: // IFL case 0x17: // IFU instructionSizeInWords += getOperandsSizeInWordsForBasicInstruction(instructionWord); break; case 0x18: // UNKNOWN case 0x19: // UNKNOWN; instructionSizeInWords += getOperandsSizeInWordsForUnknownInstruction( instructionWord ); break; case 0x1a: // ADX case 0x1b: // SBX instructionSizeInWords += getOperandsSizeInWordsForBasicInstruction(instructionWord); break; case 0x1c: // UNKNOWN case 0x1d: // UNKNOWN instructionSizeInWords += getOperandsSizeInWordsForUnknownInstruction( instructionWord ); break; case 0x1e: // STI case 0x1f: // STD instructionSizeInWords += getOperandsSizeInWordsForBasicInstruction( instructionWord ); break; default: instructionSizeInWords += getOperandsSizeInWordsForUnknownInstruction( instructionWord ); break; } return instructionSizeInWords; } private static int getOperandsSizeInWordsForBasicInstruction(int instructionWord) { // PC is already pointing at word AFTER current instruction here ! return getOperandSizeInWords(OperandPosition.SOURCE_OPERAND,instructionWord,false)+getOperandSizeInWords(OperandPosition.TARGET_OPERAND,instructionWord,false) ; } private static int getOperandsSizeInWordsForUnknownInstruction(int instructionWord) { // PC is already pointing at word AFTER current instruction here ! return 0; } private static int getOperandsSizeInWordsForSpecialInstruction(int instructionWord) { // PC is already pointing at word AFTER current instruction here ! return getOperandSizeInWords(OperandPosition.SOURCE_OPERAND,instructionWord,true); } private static int getOperandSizeInWords(OperandPosition position, int instructionWord,boolean isSpecialOpCode) { /* SET b,a * * b is always handled by the processor after a, and is the lower five bits. * In bits (in LSB-0 format), a basic instruction has the format: * * aaaaaabbbbbooooo * * b = TARGET operand * a = SOURCE operand * * Special opcodes always have their lower five bits unset, have one value and a * five bit opcode. In binary, they have the format: * * aaaaaaooooo00000 * * The value (a) is in the same six bit format as defined earlier. */ final int operandBits; if ( position == OperandPosition.SOURCE_OPERAND || isSpecialOpCode ) { // SET b , a ==> a operandBits = (instructionWord >>> 10) & ( 1+2+4+8+16+32); // SET b,a ==> b } else { operandBits = (instructionWord >>> 5) & ( 1+2+4+8+16); // SET b,a ==> b } if ( operandBits <= 0x07 ) { return 0; // operandDesc( registers[ operandBits ] ); } if ( operandBits <= 0x0f ) { return 0; // operandDesc( memory[ registers[ operandBits - 0x08 ] ] , 1 ); } if ( operandBits <= 0x17 ) { return 1; // operandDesc( memory[ registers[ operandBits - 0x10 ]+nextWord ] ,1 ); } switch( operandBits ) { case 0x18: // POP / [SP++] return 0 ; // operandDesc( memory[ sp ] , 1 ); case 0x19: return 0; // operandDesc( memory[ sp] , 1 ); case 0x1a: return 1; // operandDesc( memory[ sp + nextWord ] , 1 ); case 0x1b: return 0; // operandDesc( sp ); case 0x1c: return 0; // operandDesc( pc ); case 0x1d: return 0; // operandDesc( ex ); case 0x1e: return 1; // operandDesc( memory[ nextWord ] ,1 ); case 0x1f: return 1; // operandDesc( memory[ pc++ ] , 1 ); } // literal value: -1...30 ( 0x20 - 0x3f ) return 0; // operandDesc( operandBits - 0x21 , 0 ); } @Override public boolean canStepReturn() { return isJSR( memory.read( cpu.pc ) ); } @Override public void stepReturn() { if ( ! canStepReturn() ) { throw new IllegalStateException("PC is not at a JSR instruction, cannot skip return"); } final int currentInstructionSizeInWords = calculateInstructionSizeInWords( cpu.pc.getWordAddressValue() , memory ); final Address nextInstruction = cpu.pc.plus( Size.words( currentInstructionSizeInWords ) , true ); addBreakpoint( new OneShotBreakpoint( nextInstruction ) ); start(); } protected static boolean isJSR(int instructionWord) { final int basicOpCode = (instructionWord & 0x1f); if ( basicOpCode != 0 ) { return false; } final int specialOpCode = ( instructionWord >>> 5 ) &0x1f; return specialOpCode == 0x01; // JSR } private IDevice getDeviceForSlot(int hardwareSlot) { synchronized( devices ) { if ( hardwareSlot>=0 && hardwareSlot<devices.size()) { return devices.get(hardwareSlot); } } return null; } protected static final class OperandDesc { public final int value; public final int cycleCount; // how long it takes to perform the operation public OperandDesc(int value) { this.value = value & 0xffff; this.cycleCount = 0; } public OperandDesc(int value,int cycleCount) { this.value = value & 0xffff; this.cycleCount = cycleCount; } } protected static OperandDesc operandDesc(int value) { return new OperandDesc( value ); } protected static OperandDesc operandDesc(int value,int cycleCount) { return new OperandDesc( value , cycleCount ); } /* (non-Javadoc) * @see de.codesourcery.jasm16.emulator.IEmulator#loadMemory(de.codesourcery.jasm16.Address, byte[]) */ @Override public void loadMemory(final Address startingOffset, final byte[] data) { stop(null); if ( clockThread.isRunnable.get() ) { throw new IllegalStateException("Emulation not stopped?"); } memory.clear(); MemUtils.bulkLoad( memory , startingOffset , data ); // notify listeners listenerHelper.notifyListeners( new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { listener.afterMemoryLoad( emulator , startingOffset , data.length ); } }); } protected interface IEmulationListenerInvoker { public void invoke(IEmulator emulator, IEmulationListener listener); } /* (non-Javadoc) * @see de.codesourcery.jasm16.emulator.IEmulator#calibrate() */ @Override public synchronized void calibrate() { listenerHelper.notifyListeners( new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { listener.beforeCalibration( emulator ); } }); final double EXPECTED_CYCLES_PER_SECOND = 100000; // 100 kHz final double expectedNanosPerCycle = (1000.0d * 1000000.0d) / EXPECTED_CYCLES_PER_SECOND; out.info("Measuring delay loop..."); /* * Warm-up JVM / JIT. */ double sum =0.0d; for ( int i = 0 ; i < 5 ; i++ ) { final double tmp = clockThread.measureDelayLoopInNanos(); sum+= tmp; } /* * Repeatedly measure the execution time * for a single delay-loop iteration. */ final int LOOP_COUNT=5; sum =0.0d; for ( int i = 0 ; i < LOOP_COUNT ; i++ ) { final double tmp = clockThread.measureDelayLoopInNanos(); sum+= tmp; } final double nanosPerDelayLoopExecution = sum / LOOP_COUNT; out.info("one delay-loop iteration = "+nanosPerDelayLoopExecution+" nanoseconds."); final double loopIterationsPerCycle = expectedNanosPerCycle / nanosPerDelayLoopExecution; clockThread.adjustmentFactor = 1.0d; clockThread.oneCycleDelay = (int) Math.round( loopIterationsPerCycle ); out.info("one CPU cycle = "+clockThread.oneCycleDelay+" delay-loop iterations."); listenerHelper.notifyListeners( new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { listener.afterCalibration( emulator ); } }); } @Override public IReadOnlyCPU getCPU() { synchronized( CPU_LOCK ) { final CPU result = new CPU(this.memory); result.populateFrom( this.visibleCPU ); return result; } } @Override public IReadOnlyMemory getMemory() { return memory; } @Override public void addEmulationListener(IEmulationListener listener) { listenerHelper.addEmulationListener( listener ); } public void removeAllEmulationListeners() { listenerHelper.removeAllEmulationListeners(); } @Override public void removeEmulationListener(IEmulationListener listener) { listenerHelper.removeEmulationListener( listener ); } private Breakpoint extractRegularBreakpoint(List<Breakpoint> bps) { Breakpoint result = null; for ( Breakpoint bp : bps ) { if ( ! bp.isOneShotBreakpoint() ) { if ( result != null ) { throw new IllegalStateException("More than one " + " regular breakpoint at address "+bp.getAddress()); } result = bp; } } return result; } @Override public void addBreakpoint(final Breakpoint bp) { if (bp == null) { throw new IllegalArgumentException("breakpoint must not be NULL."); } Breakpoint replacedBreakpoint; synchronized( breakpoints ) { List<Breakpoint> list = breakpoints.get( bp.getAddress() ); if ( list == null ) { list = new ArrayList<Breakpoint>(); breakpoints.put( bp.getAddress() , list ); } replacedBreakpoint = extractRegularBreakpoint( list ); if ( replacedBreakpoint != null ) { list.remove( replacedBreakpoint ); } list.add( bp ); } // notify listeners if ( replacedBreakpoint != null && ! replacedBreakpoint.isOneShotBreakpoint() ) { listenerHelper.notifyListeners( new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { listener.breakpointDeleted( emulator , bp ); } }); } if ( ! bp.isOneShotBreakpoint() ) { listenerHelper.notifyListeners( new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { listener.breakpointAdded( emulator , bp ); } }); } } @Override public void breakpointChanged(final Breakpoint bp) { Breakpoint existing; synchronized( breakpoints ) { final List<Breakpoint> list = breakpoints.get( bp.getAddress() ); if ( list != null ) { existing = extractRegularBreakpoint( list ); } else { existing = null; } } if ( existing == null ) { LOG.warn("breakpointChanged(): Unknown breakpoint "+bp); return; } listenerHelper.notifyListeners( new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { listener.breakpointChanged( emulator , bp ); } }); } @Override public void deleteAllBreakpoints() { final List<Breakpoint> copy=new ArrayList<>(); synchronized( breakpoints ) { for ( List<Breakpoint> bp : breakpoints.values() ) { copy.addAll( bp ); } } for ( Breakpoint bp : copy ) { deleteBreakpoint( bp , false ); } listenerHelper.notifyListeners( new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { listener.allBreakpointsDeleted( emulator ); } }); } @Override public void deleteBreakpoint(final Breakpoint bp) { deleteBreakpoint(bp,true); } private void deleteBreakpoint(final Breakpoint bp,boolean notifyListeners) { if (bp == null) { throw new IllegalArgumentException("breakpoint must not be NULL."); } Breakpoint existing; synchronized( breakpoints ) { final List<Breakpoint> list = breakpoints.get( bp.getAddress() ); if ( list != null ) { final int idx = list.indexOf( bp ); if ( idx != -1 ) { existing = list.remove( idx ); } else { existing = null; } if ( list.isEmpty() ) { breakpoints.remove( bp.getAddress() ); } } else { existing = null; } } // notify listeners if ( notifyListeners && existing != null && ! existing.isOneShotBreakpoint() ) { listenerHelper.notifyListeners( new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { listener.breakpointDeleted( emulator , bp ); } }); } } @Override public List<Breakpoint> getBreakPoints() { final List<Breakpoint> result = new ArrayList<Breakpoint>(); synchronized( breakpoints ) { for ( List<Breakpoint> subList : breakpoints.values() ) { for ( Breakpoint bp : subList ) { if ( ! bp.isOneShotBreakpoint() ) { result.add( bp ); } } } } return result; } @Override public Breakpoint getBreakPoint(Address address) { if (address == null) { throw new IllegalArgumentException("address must not be NULL."); } synchronized( breakpoints ) { final List<Breakpoint> list = breakpoints.get( address ); if ( list == null ) { return null; } return extractRegularBreakpoint( list ); } } @Override public void unmapRegion(IMemoryRegion region) { synchronized( CPU_LOCK ) { this.memory.unmapRegion( region ); if ( out.isDebugEnabled() ) { out.debug("Unmapped memory region "+region); this.memory.dumpMemoryLayout(out); } } } @Override public void mapRegion(IMemoryRegion region) { synchronized( CPU_LOCK ) { this.memory.mapRegion( region ); if ( out.isDebugEnabled() ) { out.debug("Mapped memory region "+region); this.memory.dumpMemoryLayout( out ); } } } @Override public boolean triggerInterrupt(IInterrupt interrupt) { synchronized( CPU_LOCK ) { return cpu.triggerInterrupt( interrupt ); } } public int addOrReplaceDevice(IDevice device) throws DeviceErrorException { if (device == null) { throw new IllegalArgumentException("device must not be null"); } int existingSlot = -1; IDevice existingDevice = null; synchronized( devices ) { existingSlot = findDeviceSlotByDescriptor( device.getDeviceDescriptor() ); if ( existingSlot != -1 ) { existingDevice = devices.get( existingSlot ); } } final boolean requiresAdd; if ( existingDevice != null ) { // call beforeRemoveDevice() outside of synchronized block existingDevice.beforeRemoveDevice( this ); synchronized( devices ) { existingSlot = devices.indexOf( existingDevice ); if ( existingSlot == -1 ) { requiresAdd = true; } else { devices.remove( existingSlot ); devices.add( existingSlot , device ); requiresAdd = false; } } } else { requiresAdd = true; } if ( requiresAdd ) { return addDevice( device ); } // replaced device.afterAddDevice( this ); return existingSlot; } public List<IDevice> getDevicesByDescriptor(DeviceDescriptor desc) { if (desc == null) { throw new IllegalArgumentException("descriptor must not be null"); } final List<IDevice> result = new ArrayList<>(); synchronized( devices ) { for ( IDevice device : devices ) { if ( device.getDeviceDescriptor().matches( desc ) ) { result.add( device ); } } } return result; } private int findDeviceSlotByDescriptor(DeviceDescriptor descriptor) { int existingSlot = -1; synchronized( devices ) { int i = 0; for ( IDevice existing : devices ) { if ( existing.getDeviceDescriptor().matches( descriptor) ) { if ( existingSlot != -1 ) { throw new IllegalStateException("Found more than one existing device with descriptor "+descriptor ); } existingSlot = i; } i++; } } return existingSlot; } @Override public int addDevice(IDevice device) throws DeviceErrorException { if (device == null) { throw new IllegalArgumentException("device must not be null"); } final int slotNo; synchronized( devices ) { if ( ! device.supportsMultipleInstances() && findDeviceSlotByDescriptor( device.getDeviceDescriptor() ) != -1 ) { throw new IllegalStateException("Already one instance of device "+device.getDeviceDescriptor()+" registered."); } if ( devices.size() >= 65535 ) { throw new IllegalStateException("Already 65535 devices registered"); } slotNo = devices.size(); devices.add( device ); out.debug("Added device "+device); printDevices(); } boolean success = false; try { device.afterAddDevice( this ); success = true; } catch(RuntimeException e) { if ( e instanceof DeviceErrorException ) { throw e; } LOG.error("Device "+device+" failed in afterAddDevice() call",e); throw new DeviceErrorException( "afterAddDevice() failed: "+e.getMessage() , device , e ); } finally { if ( ! success ) { synchronized( devices ) { devices.remove( device ); } } } return slotNo; } private void printDevices() { synchronized( devices ) { int slot = 0; for ( IDevice d : devices ) { out.debug("Slot #"+slot+":"); out.debug( d.getDeviceDescriptor().toString(" ",true)); slot++; } } } @Override public List<IDevice> getDevices() { synchronized( devices ) { return new ArrayList<IDevice>( devices ); } } @Override public void removeDevice(IDevice device) { if (device == null) { throw new IllegalArgumentException("device must not be null"); } final boolean isRegistered; synchronized( devices ) { isRegistered = devices.contains( device ); } if ( isRegistered ) { try { device.beforeRemoveDevice( this ); } catch(RuntimeException e) { if ( e instanceof DeviceErrorException) { throw (DeviceErrorException) e; } LOG.error("Device "+device+" failed in beforeRemoveDevice() call",e); throw new DeviceErrorException( "breforeRemoveDevice() failed: "+e.getMessage() , device , e ); } } else { return; } synchronized( devices ) { devices.remove( device ); } if ( isRegistered ) { out.debug("Removed device "+device); printDevices(); } } @Override public EmulationSpeed getEmulationSpeed() { return emulationSpeed; } @Override public void setEmulationSpeed(final EmulationSpeed newSpeed) { if (newSpeed == null) { throw new IllegalArgumentException("speed must not be NULL."); } if ( this.emulationSpeed == newSpeed ) { return; } if ( newSpeed == EmulationSpeed.REAL_SPEED && ! isCalibrated() ) { calibrate(); } final EmulationSpeed oldSpeed = this.emulationSpeed; final boolean wasRunning = stop(); clockThread.changeSpeed( newSpeed ); this.emulationSpeed = newSpeed; listenerHelper.notifyListeners( new IEmulationListenerInvoker() { @Override public void invoke(IEmulator emulator, IEmulationListener listener) { listener.onEmulationSpeedChange( oldSpeed , newSpeed ); } }); if ( wasRunning ) { start(); } } @Override public boolean isCalibrated() { return clockThread.oneCycleDelay != -1; } @Override public Throwable getLastEmulationError() { return lastEmulationError; } private static <T> void safePut(BlockingQueue<T> queue, T value) { while(true) { try { queue.put(value); return; } catch (InterruptedException e) {} } } @Override public synchronized void dispose() { out.info("Disposing Emulator ..."); stop(); // terminate clock thread clockThread.sendToClockThread( Command.terminateClockThread() ); // notify listeners and remove them afterwards listenerHelper.emulatorDisposed(); // remove all devices for ( IDevice d : getDevices() ) { try { removeDevice( d ); } catch(Exception e) { LOG.error("dispose(): Failed to remove "+d); } } out.info("Emulator disposed."); } @Override public void setOutput(ILogger out) { if (out == null) { throw new IllegalArgumentException("out must not be null"); } this.loggerDelegate = out; } @Override public ILogger getOutput() { return out; } @Override public void setMemoryProtectionEnabled(boolean enabled) { checkMemoryWrites = enabled; memory.setCheckWriteAccess( enabled ); } @Override public boolean isMemoryProtectionEnabled() { return checkMemoryWrites; } @Override public void setIgnoreAccessToUnknownDevices(boolean yesNo) { this.ignoreAccessToUnknownDevices = yesNo; } protected final class CPU implements ICPU { // transient, only used inside of executeOneInstruction() code path public int currentInstructionPtr; public final MainMemory memory; /* Register A = Index 0 * Register B = Index 1 * Register C = Index 2 * Register X = Index 3 * Register Y = Index 4 * Register Z = Index 5 * Register I = Index 6 * Register J = Index 7 */ public final int[] commonRegisters = new int[ 8 ]; public int ex; public Address pc; public Address sp; public Address interruptAddress; public boolean queueInterrupts; public IInterrupt currentInterrupt; public final List<IInterrupt> interruptQueue= new ArrayList<IInterrupt>(); public int currentCycle; public CPU(MainMemory memory) { pc = sp = interruptAddress = WordAddress.ZERO; this.memory = memory; } public void populateFrom(CPU other) { System.arraycopy( other.commonRegisters , 0 , this.commonRegisters , 0 , 8 ); this.ex = other.ex; this.pc = other.pc; this.sp = other.sp; this.interruptAddress = other.interruptAddress; this.queueInterrupts = other.queueInterrupts; this.currentInterrupt = other.currentInterrupt; this.interruptQueue.clear(); this.interruptQueue.addAll(other.interruptQueue); this.currentCycle = other.currentCycle; } public boolean triggerInterrupt(IInterrupt interrupt) { if ( ! interruptsEnabled() ) { return false; } synchronized ( interruptQueue ) { if ( currentInterrupt == null && ! isQueueInterrupts() ) { currentInterrupt = interrupt; } else { // there's either already an IRQ waiting to be processed or the CPU is currently told to queue interrupts if ( interruptQueue.size() >= INTERRUPT_QUEUE_SIZE ) { throw new InterruptQueueFullException("Interrupt queue full ("+interruptQueue.size()+" entries already) , can't store "+interrupt); } setQueueInterrupts( true ); interruptQueue.add( interrupt ); } } return true; } public IInterrupt getNextProcessableInterrupt(boolean removeFromQueue) { synchronized( interruptQueue ) { if ( currentInterrupt != null ) { if ( ! removeFromQueue ) { return currentInterrupt; } final IInterrupt irq = currentInterrupt; currentInterrupt = null; return irq; } if ( ! interruptQueue.isEmpty() && ! isQueueInterrupts() ) { if ( removeFromQueue ) { return interruptQueue.remove(0); } return interruptQueue.get(0); } } return null; } public void reset() { currentCycle = 0; queueInterrupts = false; interruptQueue.clear(); sp = pc = interruptAddress = WordAddress.ZERO; ex = 0; for ( int i = 0 ; i < commonRegisters.length ; i++ ) { commonRegisters[i]=0; } } @Override public Address getPC() { return pc; } @Override public Address getSP() { return sp; } @Override public int getEX() { return ex; } @Override public Address getInterruptAddress() { return interruptAddress; } @Override public int getCurrentCycleCount() { return currentCycle; } @Override public void setRegisterValue(Register reg, int value) { switch( reg ) { case A: commonRegisters[0] = value & 0xffff; break; case B: commonRegisters[1] = value & 0xffff; break; case C: commonRegisters[2] = value & 0xffff; break; case EX: ex = value & 0xffff; break; case I: commonRegisters[6] = value & 0xffff; break; case J: commonRegisters[7] = value & 0xffff; break; case PC: pc = Address.wordAddress( value & 0xffff ); break; case SP: sp = Address.wordAddress( value & 0xffff ); break; case X: commonRegisters[3] = value & 0xffff; break; case Y: commonRegisters[4] = value & 0xffff; break; case Z: commonRegisters[5] = value & 0xffff; break; default: throw new RuntimeException("Unreachable code reached: "+reg); } } @Override public int getRegisterValue(Register reg) { switch( reg ) { case A: return commonRegisters[0]; case B: return commonRegisters[1]; case C: return commonRegisters[2]; case EX: return ex; case I: return commonRegisters[6]; case J: return commonRegisters[7]; case PC: return pc.getWordAddressValue(); case SP: return sp.getWordAddressValue(); case X: return commonRegisters[3]; case Y: return commonRegisters[4]; case Z: return commonRegisters[5]; default: throw new RuntimeException("Unreachable code reached: "+reg); } } @Override public void setQueueInterrupts(boolean yesNo) { this.queueInterrupts = yesNo; } @Override public boolean isQueueInterrupts() { return queueInterrupts; } @Override public boolean interruptsEnabled() { return interruptAddress != null && interruptAddress.getValue() != 0; } @Override public List<IInterrupt> getInterruptQueue() { return interruptQueue; } // ============== public int executeInstruction() { currentInstructionPtr = pc.getValue(); final int instructionWord = readNextWordAndAdvance(); final int opCode = (instructionWord & 0x1f); /* * |--- Basic opcodes (5 bits) ---------------------------------------------------- * |C | VAL | NAME | DESCRIPTION * +---+------+----------+--------------------------------------------------------- * |- | 0x00 | n/a | special instruction - see below * |1 | 0x01 | SET b, a | sets b to a * |2 | 0x02 | ADD b, a | sets b to b+a, sets EX to 0x0001 if there's an overflow, 0x0 otherwise * |2 | 0x03 | SUB b, a | sets b to b-a, sets EX to 0xffff if there's an underflow, 0x0 otherwise * |2 | 0x04 | MUL b, a | sets b to b*a, sets EX to ((b*a)>>16)&0xffff (treats b, a as unsigned) * |2 | 0x05 | MLI b, a | like MUL, but treat b, a as signed * |3 | 0x06 | DIV b, a | sets b to b/a, sets EX to ((b<<16)/a)&0xffff. if a==0, sets b and EX to 0 instead. (treats b, a as unsigned) * |3 | 0x07 | DVI b, a | like DIV, but treat b, a as signed. Rounds towards 0 * |3 | 0x08 | MOD b, a | sets b to b%a. if a==0, sets b to 0 instead. * |3 | 0x09 | MDI b, a | like MOD, but treat b, a as signed. Rounds towards 0 * |1 | 0x0a | AND b, a | sets b to b&a * |1 | 0x0b | BOR b, a | sets b to b|a * |1 | 0x0c | XOR b, a | sets b to b^a * |2 | 0x0d | SHR b, a | sets b to b>>>a, sets EX to ((b<<16)>>a)&0xffff (logical shift) * |2 | 0x0e | ASR b, a | sets b to b>>a, sets EX to ((b<<16)>>>a)&0xffff (arithmetic shift) (treats b as signed) * |2 | 0x0f | SHL b, a | sets b to b<<a, sets EX to ((b<<a)>>16)&0xffff * | * |2+| 0x10 | IFB b, a | performs next instruction only if (b&a)!=0 * |2+| 0x11 | IFC b, a | performs next instruction only if (b&a)==0 * |2+| 0x12 | IFE b, a | performs next instruction only if b==a * |2+| 0x13 | IFN b, a | performs next instruction only if b!=a * |2+| 0x14 | IFG b, a | performs next instruction only if b>a * |2+| 0x15 | IFA b, a | performs next instruction only if b>a (signed) * |2+| 0x16 | IFL b, a | performs next instruction only if b<a * |2+| 0x17 | IFU b, a | performs next instruction only if b<a (signed) * |- | 0x18 | - | * |- | 0x19 | - | * |3 | 0x1a | ADX b, a | sets b to b+a+EX, sets EX to 0x0001 if there is an over-flow, 0x0 otherwise * |3 | 0x1b | SBX b, a | sets b to b-a+EX, sets EX to 0xFFFF if there is an under-flow, 0x0 otherwise * |- | 0x1c | - | * |- | 0x1d | - | * |2 | 0x1e | STI b, a | sets b to a, then increases I and J by 1 * |2 | 0x1f | STD b, a | sets b to a, then decreases I and J by 1 * +---+------+----------+---------------------------------------------------------- */ switch( opCode ) { case 0x00: return handleSpecialOpCode( instructionWord ); case 0x01: return handleSET( instructionWord ); case 0x02: return handleADD( instructionWord ); case 0x03: return handleSUB( instructionWord ); case 0x04: return handleMUL( instructionWord ); case 0x05: return handleMLI( instructionWord ); case 0x06: return handleDIV( instructionWord ); case 0x07: return handleDVI( instructionWord ); case 0x08: return handleMOD( instructionWord ); case 0x09: return handleMDI( instructionWord ); case 0x0a: return handleAND( instructionWord ); case 0x0b: return handleBOR( instructionWord ); case 0x0c: return handleXOR( instructionWord ); case 0x0d: return handleSHR( instructionWord ); case 0x0e: return handleASR( instructionWord ); case 0x0f: return handleSHL( instructionWord ); case 0x10: return handleIFB( instructionWord ); case 0x11: return handleIFC( instructionWord ); case 0x12: return handleIFE( instructionWord ); case 0x13: return handleIFN( instructionWord ); case 0x14: return handleIFG( instructionWord ); case 0x15: return handleIFA( instructionWord ); case 0x16: return handleIFL( instructionWord ); case 0x17: return handleIFU( instructionWord ); case 0x18: case 0x19: return handleUnknownOpCode( instructionWord ); case 0x1a: return handleADX( instructionWord ); case 0x1b: return handleSBX( instructionWord ); case 0x1c: case 0x1d: return handleUnknownOpCode( instructionWord ); case 0x1e: return handleSTI( instructionWord ); case 0x1f: return handleSTD( instructionWord ); default: return handleUnknownOpCode( instructionWord ); } } private int handleSTD(int instructionWord) { // sets b to a, then decreases I and J by 1 // a,b,c,x,y,z,i,j final OperandDesc source = loadSourceOperand( instructionWord ); final int cycles = 2+storeTargetOperand( instructionWord , source.value )+source.cycleCount; int address = --commonRegisters[6]; // registers[6]-=1; <<< I if ( address < 0 ) { commonRegisters[6] = (int) WordAddress.MAX_ADDRESS; } address = --commonRegisters[7]; // registers[7]-=1; <<< J if ( address < 0 ) { commonRegisters[7]= (int) WordAddress.MAX_ADDRESS; } return cycles; } private int handleSTI(int instructionWord) { // sets b to a, then increases I and J by 1 // a,b,c,x,y,z,i,j final OperandDesc source = loadSourceOperand( instructionWord ); final int cycles = 2+storeTargetOperand( instructionWord , source.value )+source.cycleCount; int newWordAddress = ++commonRegisters[6]; // registers[6]+=1; <<< I if ( newWordAddress > WordAddress.MAX_ADDRESS ) { commonRegisters[6] = 0; } newWordAddress = ++commonRegisters[7]; // registers[7]+=1; <<< J if ( newWordAddress > WordAddress.MAX_ADDRESS ) { commonRegisters[7] = 0; } return cycles; } private int handleSBX(int instructionWord) { // sets b to b-a+EX, sets EX to 0xFFFF if there is an under-flow, 0x0001 if there's an overflow, 0x0 otherwise OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); final int b = target.value; final int a = source.value; int result = b-a+ex; if ( result < 0 ) { ex = 0xffff; } else if ( result > 0xffff ) { ex = 0x0001; } else { ex = 0; } if ( isEXTargetOperand( instructionWord ) ) { // Do not overwrite EX with result return 3+source.cycleCount; } return 3+storeTargetOperand( instructionWord , result & 0xffff )+source.cycleCount; } private int handleADX(int instructionWord) { // sets b to b+a+EX, sets EX to 0x0001 if there is an over-flow, 0x0 otherwise OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); final int acc = target.value + source.value + ex; if ( acc > 0xffff) { ex = 0x0001; } else { ex = 0; } if ( isEXTargetOperand( instructionWord ) ) { // Do not overwrite EX with result return 3+source.cycleCount; } return 3+storeTargetOperand( instructionWord , acc & 0xffff )+source.cycleCount; } private int handleUnknownOpCode(int instructionWord) { final Disassembler dis = new Disassembler(); // assume worst-case , each instruction only is one word final int instructionCount = Address.calcDistanceInBytes( Address.wordAddress( 0 ) , pc ).toSizeInWords().getValue(); List<DisassembledLine> lines = dis.disassemble( memory , Address.wordAddress( 0 ) , instructionCount , true ); for (DisassembledLine line : lines) { out.info( Misc.toHexString( line.getAddress() )+": "+line.getContents()); } Address lastValid = lastValidInstruction; if ( lastValid == null ) { lastValid = pc; } final String msg = "Unknown opcode 0x"+Misc.toHexString( instructionWord )+ " at address "+"0x"+Misc.toHexString( pc )+ " (last valid PC: "+Misc.toHexString( lastValid )+")"; out.warn(msg ); throw new UnknownOpcodeException( msg , instructionWord & 0xffff ); } private int handleIFU(int instructionWord) { // performs next instruction only if b<a (signed) OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , true ); int penalty = 0; if ( signed( target.value ) >= signed( source.value ) ) { penalty = handleConditionFailure(); } return 2+target.cycleCount+source.cycleCount+penalty; } private int handleConditionFailure() { boolean skippedInstructionIsConditional = isConditionalInstruction( memory.read( currentInstructionPtr ) ); currentInstructionPtr += calculateInstructionSizeInWords( currentInstructionPtr , memory ); if ( skippedInstructionIsConditional ) { do { skippedInstructionIsConditional = isConditionalInstruction( memory.read( currentInstructionPtr ) ); currentInstructionPtr += calculateInstructionSizeInWords( currentInstructionPtr , memory ); } while ( skippedInstructionIsConditional ); return 2; } return 1; } private int handleIFL(int instructionWord) { // performs next instruction only if b<a OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , true ); int penalty = 0; if ( target.value >= source.value ) { penalty = handleConditionFailure(); } return 2+target.cycleCount+source.cycleCount+penalty; } private int handleIFA(int instructionWord) { // performs next instruction only if b>a (signed) OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , true ); int penalty = 0; if ( signed( target.value ) <= signed( source.value ) ) { penalty = handleConditionFailure(); } return 2+target.cycleCount+source.cycleCount+penalty; } private int handleIFG(int instructionWord) { // performs next instruction only if b>a OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , true ); int penalty=0; if ( target.value <= source.value ) { penalty = handleConditionFailure(); } return 2+target.cycleCount+source.cycleCount+penalty; } private int handleIFN(int instructionWord) { // performs next instruction only if b!=a OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , true ); int penalty = 0; if ( target.value == source.value ) { penalty = handleConditionFailure(); } return 2+target.cycleCount+source.cycleCount+penalty; } private int handleIFE(int instructionWord) { // performs next instruction only if b==a OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , true ); int penalty=0; if ( target.value != source.value ) { penalty = handleConditionFailure(); } return 2+target.cycleCount+source.cycleCount+penalty; } private int handleIFC(int instructionWord) { // performs next instruction only if (b&a)==0 OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , true ); int penalty = 0; if ( (target.value & source.value) != 0 ) { penalty = handleConditionFailure(); } return 2+target.cycleCount+source.cycleCount+penalty; } private int handleIFB(int instructionWord) { // performs next instruction only if (b&a)!=0 OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , true ); int penalty=0; if ( (target.value & source.value) == 0 ) { penalty = handleConditionFailure(); } return 2+target.cycleCount+source.cycleCount+penalty; } private int handleSHL(int instructionWord) { // sets b to b<<a, sets EX to ((b<<a)>>16)&0xffff OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); final int acc = target.value << source.value; ex = (( target.value << source.value)>>16 ) & 0xffff; if ( isEXTargetOperand( instructionWord ) ) { // Do not overwrite EX with result return 1+source.cycleCount; } return 1+storeTargetOperand( instructionWord , acc )+source.cycleCount; } private int handleASR(int instructionWord) { // ASR b,a // arithmetic shift, sign extension !!! // sets b to b>>a, sets EX to ((b<<16)>>>a)&0xffff (arithmetic shift) (treats b as signed) OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); final int acc = signed( target.value ) >> source.value; int step1 = signed( target.value ) << 16; int step2 = step1 >> source.value; ex = step2 & 0xffff; if ( isEXTargetOperand( instructionWord ) ) { // Do not overwrite EX with result return 1+source.cycleCount; } return 1+storeTargetOperand( instructionWord , acc )+source.cycleCount; } private int handleSHR(int instructionWord) { // sets b to b>>>a, sets EX to ((b<<16)>>a)&0xffff (logical shift) OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); final int acc = target.value >>> source.value; // ex = ( ( target.value << 16) >> source.value ) int step1 = target.value << 16; int step2 = step1 >>> source.value; ex = step2 & 0xffff; if ( isEXTargetOperand( instructionWord ) ) { // Do not overwrite EX with result return 1+source.cycleCount; } return 1+storeTargetOperand( instructionWord , acc )+source.cycleCount; } private int handleDVI(int instructionWord) { // ((b<<16)/a)&0xffff // e DIV, but treat b, a as signed. Rounds towards 0 OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); int acc; if ( source.value == 0 ) { ex = 0; acc=0; } else { acc = signed( target.value ) / signed( source.value ); // ((b<<16)/a)&0xffff int step1 = signed( target.value ) << 16; int step2 = step1 / signed(source.value ); ex = step2 & 0xffff; if ( ex == 0 && ! isSignBitSet( target.value ) && isSignBitSet( source.value ) ) { ex = 0xffff; } } if ( isEXTargetOperand( instructionWord ) ) { // Do not overwrite EX with result return 3+source.cycleCount; } return 3+storeTargetOperand( instructionWord , acc )+source.cycleCount; } private int handleXOR(int instructionWord) { // sets b to b^a OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); final int acc = target.value ^ source.value; return 1+storeTargetOperand( instructionWord , acc )+source.cycleCount; } private int handleBOR(int instructionWord) { // sets b to b|a OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); final int acc = target.value | source.value; return 1+storeTargetOperand( instructionWord , acc )+source.cycleCount; } private int handleAND(int instructionWord) { // sets b to b&a OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); final int acc = target.value & source.value; return 1+storeTargetOperand( instructionWord , acc )+source.cycleCount; } private int handleMDI(int instructionWord) { // like MOD, but treat b, a as signed. Rounds towards 0 OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); final int acc; if ( source.value == 0 ) { acc=0; } else { acc = signed( target.value ) % signed( source.value ); } return 3+storeTargetOperand( instructionWord , acc )+source.cycleCount; } private int handleMOD(int instructionWord) { // sets b to b%a. if a==0, sets b to 0 instead. OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); final int acc; if ( source.value == 0 ) { acc=0; } else { acc = target.value % source.value; } return 3+storeTargetOperand( instructionWord , acc )+source.cycleCount; } private int handleDIV(int instructionWord) { /* set b (TARGET) ,a (SOURCE) * sets b to b/a, sets EX to ((b<<16)/a)&0xffff. if a==0, sets b and EX to 0 instead. (treats b, a as unsigned) */ OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); final int acc; if ( source.value == 0 ) { ex = 0; acc=0; } else { acc = target.value / source.value; long tv = target.value; long sv = source.value; ex = (int) ( (( tv << 16) / sv ) & 0xffff); } if ( isEXTargetOperand( instructionWord ) ) { // Do not overwrite EX with result return 3+source.cycleCount; } return 3+storeTargetOperand( instructionWord , acc )+source.cycleCount; } private int handleMLI(int instructionWord) { // like MUL, but treat b, a as signed OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); final int a = signed( target.value ); final int b = signed( source.value ); final int acc = a * b; ex = ((a * b) >> 16 ) & 0xffff; if ( isEXTargetOperand( instructionWord ) ) { // Do not overwrite EX with result return 2+source.cycleCount; } return 2+storeTargetOperand( instructionWord , acc )+source.cycleCount; } private int signed( int value) { if ( ( value & ( 1 << 15 ) ) != 0 ) { // MSB set => negative value return 0xffff0000 | value; } return value; } private boolean isSignBitSet(int value) { return ( value & ( 1 << 15 ) ) != 0; } private int handleMUL(int instructionWord) { // sets b to b*a, sets EX to ((b*a)>>16)&0xffff (treats b, a as unsigned) OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); final int acc = target.value * source.value; ex = ((target.value * source.value) >> 16 ) & 0xffff; if ( isEXTargetOperand( instructionWord ) ) { // Do not overwrite EX with result return 2+source.cycleCount; } return 2+storeTargetOperand( instructionWord , acc )+source.cycleCount; } private int handleSUB(int instructionWord) { // sets b to b-a, sets EX to 0xffff if there's an underflow, 0x0 otherwise OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); final int acc = target.value - source.value; if ( acc < 0 ) { ex = 0xffff; } else { ex = 0; } if ( isEXTargetOperand( instructionWord ) ) { // Do not overwrite EX with result return 2+source.cycleCount; } return 2+storeTargetOperand( instructionWord , acc )+source.cycleCount; } private int handleADD(int instructionWord) { // sets b to b+a, sets EX to 0x0001 if there's an overflow, 0x0 otherwise OperandDesc source = loadSourceOperand( instructionWord ); OperandDesc target = loadTargetOperand( instructionWord , false , false ); final int acc = target.value + source.value; if ( acc > 0xffff) { ex = 0x0001; } else { ex = 0; } if ( isEXTargetOperand( instructionWord ) ) { // Do not overwrite EX with result return 2+source.cycleCount; } return 2+storeTargetOperand( instructionWord , acc )+source.cycleCount; } private int handleSET(int instructionWord) { final OperandDesc desc = loadSourceOperand( instructionWord ); return 1+storeTargetOperand( instructionWord , desc.value ) + desc.cycleCount; } private int handleSpecialOpCode(int instructionWord) { final int opCode = ( instructionWord >>> 5 ) &0x1f; /* * |--- Special opcodes: (5 bits) -------------------------------------------------- * | C | VAL | NAME | DESCRIPTION * |---+------+-------+------------------------------------------------------------- * | - | 0x00 | n/a | reserved for future expansion * | 3 | 0x01 | JSR a | pushes the address of the next instruction to the stack, then sets PC to a * | - | 0x02 | - | * | - | 0x03 | - | * | - | 0x04 | - | * | - | 0x05 | - | * | - | 0x06 | - | * | 9 | 0x07 | HCF a | use sparingly * | 4 | 0x08 | INT a | triggers a software interrupt with message a * | 1 | 0x09 | IAG a | sets a to IA * | 1 | 0x0a | IAS a | sets IA to a * | 3 | 0x0b | IAP a | if IA is 0, does nothing, otherwise pushes IA to the stack, * | | | | then sets IA to a * | 2 | 0x0c | IAQ a | if a is nonzero, interrupts will be added to the queue * | | | | instead of triggered. if a is zero, interrupts will be * | | | | triggered as normal again * | - | 0x0d | - | * | - | 0x0e | - | * | - | 0x0f | - | * | 2 | 0x10 | HWN a | sets a to number of connected hardware devices * | 4 | 0x11 | HWQ a | sets A, B, C, X, Y registers to information about hardware a * | | | | A+(B<<16) is a 32 bit word identifying the hardware id * | | | | C is the hardware version * | | | | X+(Y<<16) is a 32 bit word identifying the manufacturer * | 4+| 0x12 | HWI a | sends an interrupt to hardware a * | - | 0x13 | - | * | - | 0x14 | - | * | - | 0x15 | - | * | - | 0x16 | - | * | - | 0x17 | - | * | - | 0x18 | - | * | - | 0x19 | - | * | - | 0x1a | - | * | - | 0x1b | - | * | - | 0x1c | - | * | - | 0x1d | - | * | - | 0x1e | - | * | - | 0x1f | - | * |---+------+-------+------------------------------------------------------------- */ switch( opCode ) { case 0x00: return handleUnknownOpCode( instructionWord ); case 0x01: return handleJSR( instructionWord ); case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: return handleUnknownOpCode( instructionWord ); case 0x07: // HCF was removed in spec 1.7 // return handleHCF( instructionWord ); return handleUnknownOpCode( instructionWord ); case 0x08: return handleINT( instructionWord ); case 0x09: return handleIAG( instructionWord ); case 0x0a: return handleIAS( instructionWord ); case 0x0b: return handleRFI( instructionWord ); case 0x0c: return handleIAQ( instructionWord ); case 0x0d: case 0x0e: case 0x0f: return handleUnknownOpCode( instructionWord ); case 0x10: return handleHWN( instructionWord ); case 0x11: return handleHWQ( instructionWord ); case 0x12: return handleHWI( instructionWord ); case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: default: return handleUnknownOpCode( instructionWord ); } } private int handleHWI(int instructionWord) { final OperandDesc operand = loadSourceOperand(instructionWord); final int hardwareSlot = operand.value; final IDevice device = getDeviceForSlot( hardwareSlot ); final int cyclesConsumed; if ( device == null ) { if ( ! ignoreAccessToUnknownDevices ) { LOG.error("handleHWI(): No device at slot #"+hardwareSlot); out.warn("No device at slot #"+hardwareSlot); throw new InvalidDeviceSlotNumberException("No device at slot #"+hardwareSlot); } cyclesConsumed = 0; } else { try { cyclesConsumed = device.handleInterrupt( Emulator.this, this , this.memory ); } catch(RuntimeException e) { if ( e instanceof DeviceErrorException) { throw e; } LOG.error("handleHWI(): Device "+device+" failed in handleInterrupt(): "+e.getMessage() , e ); throw new DeviceErrorException("Device "+device+" failed in handleInterrupt(): "+e.getMessage() , device , e ); } } return 4+operand.cycleCount+cyclesConsumed; } /* * * b is always handled by the processor after a, and is the lower five bits. * In bits (in LSB-0 format), a basic instruction has the format: * * aaaaaabbbbbooooo * * SET b,a * * b = TARGET operand * a = SOURCE operand * * --- Values: (5/6 bits) --------------------------------------------------------- * * | C | VALUE | DESCRIPTION * +---+-----------+---------------------------------------------------------------- * | 0 | 0x00-0x07 | register (A, B, C, X, Y, Z, I or J, in that order) * | 0 | 0x08-0x0f | [register] * | 1 | 0x10-0x17 | [register + next word] * | 0 | 0x18 | (PUSH / [--SP]) if in TARGET, or (POP / [SP++]) if in SOURCE * | 0 | 0x19 | [SP] / PEEK * | 1 | 0x1a | [SP + next word] / PICK n * | 0 | 0x1b | SP * | 0 | 0x1c | PC * | 0 | 0x1d | EX * | 1 | 0x1e | [next word] * | 1 | 0x1f | next word (literal) * | 0 | 0x20-0x3f | literal value 0xffff-0x1e (-1..30) (literal) (only for SOURCE) * +---+-----------+---------------------------------------------------------------- */ private int storeTargetOperand(int instructionWord,int value) { return storeTargetOperand( instructionWord , value , false ); } private int storeTargetOperand(int instructionWord,int value,boolean isSpecialOpcode) { final int operandBits; /* * Special opcodes always have their lower five bits unset, have one value and a * five bit opcode. * * In binary, they have the format: aaaaaaooooo00000 */ if ( isSpecialOpcode ) { operandBits = (instructionWord >>> 10) & ( 1+2+4+8+16+32); } else { operandBits = (instructionWord >>> 5) & ( 1+2+4+8+16); } if ( operandBits <= 07 ) { commonRegisters[ operandBits ] = value & 0xffff; return 0; } if ( operandBits <= 0x0f ) { memory.write( commonRegisters[ operandBits - 0x08 ] , value); return 1; } if ( operandBits <= 0x17 ) { final int nextWord = readNextWordAndAdvance(); writeMemoryWithOffsetAndWrapAround( commonRegisters[ operandBits - 0x10 ] , nextWord , value); return 1; } switch( operandBits ) { case 0x18: // (PUSH / [--SP]) if in b, or (POP / [SP++]) if in a push( value ); return 0; case 0x19: // PEEK/[SP] memory.write( sp , value ); return 0; case 0x1a: int nextWord = readNextWordAndAdvance(); Address dst = sp.plus( Address.wordAddress( nextWord ) , true); memory.write( dst , value ); return 1; case 0x1b: sp = Address.wordAddress( value & 0xffff ); return 0; case 0x1c: // PC currentInstructionPtr = value & 0xffff; return 0; case 0x1d: ex = value & 0xffff; return 0; case 0x1e: nextWord = readNextWordAndAdvance(); memory.write( nextWord , value); return 1; default: // store to immediate value like 'SET 0x1000 , A' if ( haltOnStoreToImmediateValue ) { final String msg = "(store to immediate value) Illegal target operand in instruction word 0x"+ Misc.toHexString( instructionWord )+" at address 0x"+Misc.toHexString( pc ); LOG.error("handleIllegalTargetOperand(): "+msg); out.warn( msg); throw new InvalidTargetOperandException( msg ); } return 0; } } private int readNextWordAndAdvance() { final int word = memory.read( currentInstructionPtr ); currentInstructionPtr = (currentInstructionPtr+1) & 0xffff; return word; } private OperandDesc loadSourceOperand(int instructionWord) { /* SET b,a * * b = TARGET operand * a = SOURCE operand * * Special opcodes always have their lower five bits unset, have one value and a * five bit opcode. In binary, they have the format: aaaaaaooooo00000 * The value (a) is in the same six bit format as defined earlier. */ final int operandBits= (instructionWord >>> 10) & ( 1+2+4+8+16+32); if ( operandBits <= 0x07 ) { return operandDesc( commonRegisters[ operandBits ] ); } if ( operandBits <= 0x0f ) { return operandDesc( memory.read( commonRegisters[ operandBits - 0x08 ] ) , 1 ); } if ( operandBits <= 0x17 ) { final int nextWord = readNextWordAndAdvance(); return operandDesc( readMemoryWithOffsetAndWrapAround( commonRegisters[ operandBits - 0x10 ] , nextWord ) ,1 ); } switch( operandBits ) { case 0x18: // (PUSH / [--SP]) if in b, or (POP / [SP++]) if in a final OperandDesc tmp = operandDesc( memory.read( sp ) ); sp = sp.incrementByOne(true); return tmp; case 0x19: return operandDesc( memory.read( sp ) , 1 ); case 0x1a: int nextWord = readNextWordAndAdvance(); final Address dst = sp.plus( Address.wordAddress( nextWord ) , true ); return operandDesc( memory.read( dst ) , 1 ); case 0x1b: return operandDesc( sp.getValue() ); case 0x1c: return operandDesc( currentInstructionPtr ); case 0x1d: return operandDesc( ex ); case 0x1e: nextWord = readNextWordAndAdvance(); return operandDesc( memory.read( nextWord ) ,1 ); case 0x1f: return operandDesc( readNextWordAndAdvance() , 1 ); } // literal value: -1...30 ( 0x20 - 0x3f ) return operandDesc( ( operandBits - 0x21 ) , 0 ); } private int readMemoryWithOffsetAndWrapAround(int address,int offset) { int realAddress =(int) ( ( address + offset ) % (WordAddress.MAX_ADDRESS +1 ) ); return memory.read( realAddress ); } private void writeMemoryWithOffsetAndWrapAround(int address,int offset,int value) { int realAddress =(int) ( ( address + offset ) % (WordAddress.MAX_ADDRESS +1 ) ); memory.write( realAddress , value ); } protected final boolean isEXTargetOperand(int instructionWord) { final int operandBits; if ( (instructionWord & 0b11111) == 0 ) { // special instruction operandBits= (instructionWord >>> 10) & ( 1+2+4+8+16+32); } else { operandBits= (instructionWord >>> 5) & ( 1+2+4+8+16); } return operandBits == 0x1d; } /** * * @param instructionWord * @param specialInstruction * @param performIncrementDecrement Whether this invocation should also increment/decrement registers as necessary or * whether this is handled by the caller (because a subsequent STORE will be performed ) * @return */ protected OperandDesc loadTargetOperand(int instructionWord,boolean specialInstruction,boolean performIncrementDecrement) { /* * SET b,a * * b = TARGET operand * a = SOURCE operand * * SOURCE is always handled by the processor BEFORE TARGET, and is the lower five bits. * In bits (in LSB-0 format), a basic instruction has the format: * * aaaaaabbbbbooooo * * SPECIAL opcodes always have their lower five bits unset, have one value and a * five bit opcode. In binary, they have the format: * * aaaaaaooooo00000 * * The value (a) is in the same six bit format as defined earlier. */ final int operandBits; if ( specialInstruction ) { operandBits= (instructionWord >>> 10) & ( 1+2+4+8+16+32); } else { operandBits= (instructionWord >>> 5) & ( 1+2+4+8+16); } if ( operandBits <= 0x07 ) { return operandDesc( commonRegisters[ operandBits ] ); } if ( operandBits <= 0x0f ) { return operandDesc( memory.read( commonRegisters[ operandBits - 0x08 ] ) , 1 ); } if ( operandBits <= 0x17 ) { final int nextWord; if ( performIncrementDecrement ) { nextWord = readNextWordAndAdvance(); } else { nextWord = memory.read( currentInstructionPtr ); } return operandDesc( readMemoryWithOffsetAndWrapAround( commonRegisters[ operandBits - 0x10 ] , nextWord ) ,1 ); } switch( operandBits ) { case 0x18: // (PUSH / [--SP++]) if in b return operandDesc( memory.read( sp.decrementByOne() ) , 1 ); case 0x19: return operandDesc( memory.read( sp ) , 1 ); case 0x1a: int nextWord = 0; if ( performIncrementDecrement ) { nextWord = readNextWordAndAdvance(); } else { nextWord = memory.read( currentInstructionPtr ); } final Address dst = sp.plus( Address.wordAddress( nextWord ) , true ); return operandDesc( memory.read( dst ) , 1 ); case 0x1b: return operandDesc( sp.getValue() ); case 0x1c: return operandDesc( currentInstructionPtr ); case 0x1d: return operandDesc( ex ); case 0x1e: if ( performIncrementDecrement ) { nextWord = readNextWordAndAdvance(); } else { nextWord = memory.read( currentInstructionPtr ); } return operandDesc( memory.read( nextWord ) ,1 ); case 0x1f: if ( performIncrementDecrement ) { nextWord = readNextWordAndAdvance(); } else { nextWord = memory.read( currentInstructionPtr ); } return operandDesc( nextWord , 1 ); } // literal value: -1...30 ( 0x20 - 0x3f ) return operandDesc( operandBits - 0x21 , 0 ); } private int handleHWQ(int instructionWord) { // Sets A, B, C, X, Y registers to information about hardware a. final OperandDesc operand = loadSourceOperand(instructionWord); final int hardwareSlot = operand.value; final IDevice device = getDeviceForSlot( hardwareSlot ); if ( device == null ) { if ( ! ignoreAccessToUnknownDevices ) { LOG.error("handleHWQ(): No device at slot #"+hardwareSlot); out.warn("No device at slot #"+hardwareSlot); throw new InvalidDeviceSlotNumberException("No device at slot #"+hardwareSlot); } commonRegisters[0] = 0xffff; commonRegisters[1] = 0xffff; commonRegisters[2] = 0xffff; commonRegisters[3] = 0xffff; commonRegisters[4] = 0xffff; } else { /* sets A, B, C, X, Y registers to information about hardware a * * A+(B<<16) is a 32 bit word identifying the hardware id * C is the hardware version * X+(Y<<16) is a 32 bit word identifying the manufacturer */ /* A+(B<<16) is a 32 bit word identifying the hardware id * * A = LSB hardware ID (16 bit) * B = MSB hardware ID (16 bit) * C = hardware version * X = LSB manufacturer ID (16 bit) * Y = MSB manufacturer ID (16 bit) */ final DeviceDescriptor descriptor = device.getDeviceDescriptor(); commonRegisters[0] = (int) descriptor.getID() & 0xffff; commonRegisters[1] = (int) ( ( descriptor.getID() >>> 16 ) & 0xffff ); commonRegisters[2] = descriptor.getVersion() & 0xffff; commonRegisters[3] = (int) descriptor.getManufacturer() & 0xffff; commonRegisters[4] = (int) ( ( descriptor.getManufacturer() >>> 16 ) & 0xffff ); } return 4+operand.cycleCount; } private int handleHWN(int instructionWord) { // sets a to number of connected hardware devices final int deviceCount; synchronized( devices ) { deviceCount=devices.size(); } return 2 + storeTargetOperand( instructionWord , deviceCount , true ); } private int handleIAQ(int instructionWord) { /* if a is nonzero, interrupts will be added to the queue * instead of triggered. if a is zero, interrupts will be * triggered as normal again */ final OperandDesc operand = loadSourceOperand( instructionWord ); setQueueInterrupts( operand.value != 0 ); return 2+operand.cycleCount; } private int handleRFI(int instructionWord) { /* * 3 | 0x0b | RFI a | disables interrupt queueing, pops A from the stack, then | | | pops PC from the stack */ setQueueInterrupts( false ); commonRegisters[0] = pop(); // pop A from stack currentInstructionPtr = pop(); // pop PC from stack return 3; } private int handleIAS(int instructionWord) { // IAS a => sets IA to a final OperandDesc operand = loadSourceOperand( instructionWord ); interruptAddress = Address.wordAddress( operand.value ); return 1+operand.cycleCount; } private int handleIAG(int instructionWord) { // IAG a => sets A to IA return 1+storeTargetOperand( instructionWord , interruptAddress.getValue() , true ); } private int handleINT(int instructionWord) { final OperandDesc operand = loadSourceOperand( instructionWord ); triggerInterrupt(new SoftwareInterrupt( operand.value )); return 4+operand.cycleCount; } private int handleJSR(int instructionWord) { // pushes the address of the next instruction to the stack, then sets PC to a OperandDesc source= loadSourceOperand( instructionWord ); push( currentInstructionPtr ); currentInstructionPtr = source.value; return 3+source.cycleCount; } private int pop() { // SET a, [SP++] final int result = memory.read( sp ); sp= sp.incrementByOne(true); return result; } private void push(int value) { // SET [--SP] , blubb sp = sp.decrementByOne(); memory.write( sp , value ); } public boolean maybeProcessOneInterrupt() { if ( interruptsEnabled() ) { final IInterrupt irq = getNextProcessableInterrupt( true ); if ( irq != null ) { // push PC to stack // SET [ --SP ] , PC push( pc.getValue() ); // push A to stack push( commonRegisters[0] ); pc = interruptAddress; commonRegisters[0] = irq.getMessage() & 0xffff; return true; } } return false; } } // end of class: CPU @Override public boolean isCrashOnStoreWithImmediate() { return haltOnStoreToImmediateValue; } @Override public void setCrashOnStoreWithImmediate(boolean doCrashOnImmediate) { this.haltOnStoreToImmediateValue = doCrashOnImmediate; } }