/********************************************************************************
* The contents of this file are subject to the GNU General Public License *
* (GPL) Version 2 or later (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.gnu.org/copyleft/gpl.html *
* *
* Software distributed under the License is distributed on an "AS IS" basis, *
* without warranty of any kind, either expressed or implied. See the License *
* for the specific language governing rights and limitations under the *
* License. *
* *
* This file was originally developed as part of the software suite that *
* supports the book "The Elements of Computing Systems" by Nisan and Schocken, *
* MIT Press 2005. If you modify the contents of this file, please document and *
* mark your changes clearly, for the benefit of others. *
********************************************************************************/
package Hack.CPUEmulator;
import Hack.ComputerParts.Bus;
import Hack.ComputerParts.ComputerPartErrorEvent;
import Hack.ComputerParts.ComputerPartErrorEventListener;
import Hack.ComputerParts.Register;
import Hack.Controller.*;
import Hack.Events.ProgramEvent;
import Hack.Utilities.Conversions;
import Hack.Utilities.Definitions;
/**
* A CPU Emulator. Emulates machine code (In HACK format).
*
* Recognizes the following variables:
* A - the address register (short)
* D - the data register (short)
* PC - the program counter (short)
* RAM[i] - the contents of the RAM at location i (short)
* ROM[i] - the contents of the ROM at location i (short)
* time - the time that passed since the program started running (long) - READ ONLY
*
* Recognizes the following commands:
* load <HACK file name> - loads the given file into the ROM
* TickTock - advances the clock by one time unit (executes one instruction)
*/
public class CPUEmulator extends HackSimulator implements ComputerPartErrorEventListener {
// Variables
private static final String VAR_A = "A";
private static final String VAR_D = "D";
private static final String VAR_PC = "PC";
private static final String VAR_RAM = "RAM";
private static final String VAR_ROM = "ROM";
private static final String VAR_TIME = "time";
// Commands
private static final String COMMAND_TICKTOCK = "ticktock";
private static final String COMMAND_ROMLOAD = "load";
private static final String COMMAND_SETVAR = "set";
// The simulating cpu
private CPU cpu;
// The GUI of the CPUEmulator
private CPUEmulatorGUI gui;
// The list of recognized variables.
private String[] vars;
// The keyboard
private Keyboard keyboard;
// The current animation mode
private int animationMode;
/**
* Constructs a new CPU Emulator with no GUI component.
*/
public CPUEmulator() {
RAM ram = new RAM(null, null, null);
ram.reset();
ROM rom = new ROM(null);
rom.reset();
PointerAddressRegisterAdapter A = new PointerAddressRegisterAdapter(null, ram);
A.reset();
Register D = new Register(null);
D.reset();
PointerAddressRegisterAdapter PC = new PointerAddressRegisterAdapter(null, rom);
PC.reset();
keyboard = new Keyboard(ram, null);
keyboard.reset();
ALU alu = new ALU(null);
alu.reset();
Bus bus = new Bus(null);
bus.reset();
cpu = new CPU(ram, rom, A, D, PC, alu, bus);
init();
}
/**
* Constructs a new CPU Emulator with the given GUI component.
*/
public CPUEmulator(CPUEmulatorGUI gui) {
this.gui = gui;
RAM ram = new RAM(gui.getRAM(), null, gui.getScreen());
ram.addErrorListener(this);
ram.reset();
ROM rom = new ROM(gui.getROM());
rom.addErrorListener(this);
rom.addProgramListener(this); // listens to program file change
rom.reset();
PointerAddressRegisterAdapter A = new PointerAddressRegisterAdapter(gui.getA(), ram);
A.addErrorListener(this);
A.reset();
Register D = new Register(gui.getD());
D.addErrorListener(this);
D.reset();
PointerAddressRegisterAdapter PC = new PointerAddressRegisterAdapter(gui.getPC(), rom);
PC.addErrorListener(this);
PC.reset();
keyboard = new Keyboard(ram, gui.getKeyboard());
keyboard.reset();
ALU alu = new ALU(gui.getALU());
alu.reset();
Bus bus = new Bus(gui.getBus());
bus.reset();
cpu = new CPU(ram, rom, A, D, PC, alu, bus);
init();
}
// Initializes the emulator
private void init() {
vars = new String[]{VAR_A, VAR_D, VAR_PC, VAR_RAM + "[]", VAR_ROM + "[]", VAR_TIME};
}
public String getName() {
return "CPU Emulator";
}
/**
* Returns the value of the given variable.
* Throws VariableException if the variable is not legal.
*/
public String getValue(String varName) throws VariableException {
if (varName.equals(VAR_A))
return String.valueOf(cpu.getA().get());
else if (varName.equals(VAR_D))
return String.valueOf(cpu.getD().get());
else if (varName.equals(VAR_PC))
return String.valueOf(cpu.getPC().get());
else if (varName.equals(VAR_TIME))
return String.valueOf(cpu.getTime());
else if (varName.startsWith(VAR_RAM + "[")) {
short index = getRamIndex(varName);
return String.valueOf(cpu.getRAM().getValueAt(index));
}
else if (varName.startsWith(VAR_ROM + "[")) {
short index = getRomIndex(varName);
return String.valueOf(cpu.getROM().getValueAt(index));
}
else
throw new VariableException("Unknown variable", varName);
}
/**
* Sets the given variable with the given value.
* Throws VariableException if the variable name or value are not legal.
*/
public void setValue(String varName, String value) throws VariableException {
int numValue;
try {
value = Conversions.toDecimalForm(value);
if (varName.equals(VAR_A)) {
numValue = Integer.parseInt(value);
check_ram_address(varName, numValue);
cpu.getA().store((short)numValue);
}
else if (varName.equals(VAR_D)) {
numValue = Integer.parseInt(value);
check_value(varName, numValue);
cpu.getD().store((short)numValue);
}
else if (varName.equals(VAR_PC)) {
numValue = Integer.parseInt(value);
check_rom_address(varName, numValue);
cpu.getPC().store((short)numValue);
}
else if (varName.equals(VAR_TIME))
throw new VariableException("Read Only variable", varName);
else if (varName.startsWith(VAR_RAM + "[")) {
short index = getRamIndex(varName);
numValue = Integer.parseInt(value);
check_value(varName, numValue);
cpu.getRAM().setValueAt(index, (short)numValue, false);
}
else if (varName.startsWith(VAR_ROM + "[")) {
short index = getRomIndex(varName);
numValue = Integer.parseInt(value);
check_value(varName, numValue);
cpu.getROM().setValueAt(index, (short)numValue, false);
}
else
throw new VariableException("Unknown variable", varName);
} catch (NumberFormatException nfe) {
throw new VariableException("'" + value + "' is not a legal value for variable",
varName);
}
}
/**
* Executes the given simulator command (given in args[] style).
* Throws CommandException if the command is not legal.
* Throws ProgramException if an error occurs in the program.
*/
public void doCommand(String[] command)
throws CommandException, ProgramException, VariableException {
if (command.length == 0)
throw new CommandException("Empty command", command);
// hide gui highlights
if (animationMode != HackController.NO_DISPLAY_CHANGES)
hideHighlightes();
// execute the appropriate command
if (command[0].equals(COMMAND_TICKTOCK)) {
if (command.length != 1)
throw new CommandException("Illegal number of arguments to command", command);
cpu.executeInstruction();
}
else if (command[0].equals(COMMAND_SETVAR)) {
if (command.length != 3)
throw new CommandException("Illegal number of arguments to command", command);
setValue(command[1], command[2]);
}
else if (command[0].equals(COMMAND_ROMLOAD)) {
if (command.length != 2)
throw new CommandException("Illegal number of arguments to command", command);
String fileName = getWorkingDir().getAbsolutePath() + "/" + command[1];
cpu.getROM().loadProgram(fileName);
int oldAnimationMode = animationMode;
setAnimationMode(HackController.DISPLAY_CHANGES);
cpu.initProgram();
setAnimationMode(oldAnimationMode);
}
else
throw new CommandException("Unknown simulator command", command);
}
// Hides all highlights in GUIs.
private void hideHighlightes() {
cpu.getRAM().hideHighlight();
cpu.getROM().hideHighlight();
cpu.getA().hideHighlight();
cpu.getD().hideHighlight();
cpu.getPC().hideHighlight();
cpu.getALU().hideHighlight();
}
/**
* Restarts the CPUEmulator - program will be restarted.
*/
public void restart() {
cpu.initProgram();
}
public void setAnimationMode(int newAnimationMode) {
if (gui != null) {
// enter NO_DISPLAY_CHANGES
if (newAnimationMode == HackController.NO_DISPLAY_CHANGES &&
animationMode != HackController.NO_DISPLAY_CHANGES) {
cpu.getRAM().disableUserInput();
cpu.getROM().disableUserInput();
cpu.getA().disableUserInput();
cpu.getD().disableUserInput();
cpu.getPC().disableUserInput();
ScreenGUI screen = gui.getScreen();
if (screen != null)
screen.startAnimation();
}
// exit NO_DISPLAY_CHANGES
if (newAnimationMode != HackController.NO_DISPLAY_CHANGES &&
animationMode == HackController.NO_DISPLAY_CHANGES) {
cpu.getRAM().enableUserInput();
cpu.getROM().enableUserInput();
cpu.getA().enableUserInput();
cpu.getD().enableUserInput();
cpu.getPC().enableUserInput();
ScreenGUI screen = gui.getScreen();
if (screen != null)
screen.stopAnimation();
}
}
animationMode = newAnimationMode;
boolean animate = (animationMode == HackController.ANIMATION);
cpu.getBus().setAnimate(animate);
cpu.getRAM().setAnimate(animate);
cpu.getROM().setAnimate(animate);
cpu.getA().setAnimate(animate);
cpu.getD().setAnimate(animate);
cpu.getPC().setAnimate(animate);
cpu.getALU().setAnimate(animate);
boolean displayChanges = (animationMode != HackController.NO_DISPLAY_CHANGES);
cpu.getRAM().setDisplayChanges(displayChanges);
cpu.getROM().setDisplayChanges(displayChanges);
cpu.getA().setDisplayChanges(displayChanges);
cpu.getD().setDisplayChanges(displayChanges);
cpu.getPC().setDisplayChanges(displayChanges);
cpu.getALU().setDisplayChanges(displayChanges);
}
public void setNumericFormat(int formatCode) {
cpu.getRAM().setNumericFormat(formatCode);
cpu.getA().setNumericFormat(formatCode);
cpu.getD().setNumericFormat(formatCode);
cpu.getPC().setNumericFormat(formatCode);
cpu.getALU().setNumericFormat(formatCode);
}
public void setAnimationSpeed(int speedUnit) {
cpu.getBus().setAnimationSpeed(speedUnit);
}
public int getInitialAnimationMode() {
return HackController.DISPLAY_CHANGES;
}
public int getInitialNumericFormat() {
return HackController.DECIMAL_FORMAT;
}
public void refresh() {
cpu.getBus().refreshGUI();
cpu.getRAM().refreshGUI();
cpu.getROM().refreshGUI();
cpu.getA().refreshGUI();
cpu.getD().refreshGUI();
cpu.getPC().refreshGUI();
cpu.getALU().refreshGUI();
ScreenGUI screen = gui.getScreen();
if (screen != null)
screen.refresh();
}
public void prepareFastForward() {
gui.requestFocus();
keyboard.requestFocus();
}
public void prepareGUI() {
}
public String[] getVariables() {
return vars;
}
protected HackSimulatorGUI getGUI() {
return gui;
}
/**
* Called when the ROM's current program is changed.
* The event contains the source object, event type and the new program's file name (if any).
*/
public void programChanged(ProgramEvent event) {
super.programChanged(event);
if (event.getType() == ProgramEvent.LOAD) {
int oldAnimationMode = animationMode;
setAnimationMode(HackController.DISPLAY_CHANGES);
refresh();
notifyListeners(ControllerEvent.ENABLE_MOVEMENT, null);
cpu.initProgram();
setAnimationMode(oldAnimationMode);
}
}
/**
* Called when an error occured in a computer part.
* The event contains the source computer part and the error message.
*/
public void computerPartErrorOccured(ComputerPartErrorEvent event) {
displayMessage(event.getErrorMessage(), true);
}
// receives a variable name of the form xxx[i] and returns the numeric
// value of i, which is an address in the RAM.
// Throws VariableException if i is not a legal address in the RAM.
private static short getRamIndex(String varName) throws VariableException {
if (varName.indexOf("]") == -1)
throw new VariableException("Missing ']'", varName);
String indexStr = varName.substring(varName.indexOf("[") + 1, varName.indexOf("]"));
int index = Integer.parseInt(indexStr);
if (index < 0 || index >= Definitions.RAM_SIZE)
throw new VariableException("Illegal variable index", varName);
return (short)index;
}
// receives a variable name of the form xxx[i] and returns the numeric
// value of i, which is an address in the ROM.
// Throws VariableException if i is not a legal address in the ROM.
private static short getRomIndex(String varName) throws VariableException {
if (varName.indexOf("]") == -1)
throw new VariableException("Missing ']'", varName);
String indexStr = varName.substring(varName.indexOf("[") + 1, varName.indexOf("]"));
int index = Integer.parseInt(indexStr);
if (index < 0 || index >= Definitions.ROM_SIZE)
throw new VariableException("Illegal variable index", varName);
return (short)index;
}
// Checks that the given value is a legal 16-bit value
private void check_value(String varName, int value) throws VariableException {
if (value < -32768 || value >= 32768)
throw new VariableException(value +
" is an illegal value for variable", varName);
}
// Checks that the given value is a legal 16-bit address
private void check_ram_address(String varName, int value) throws VariableException {
if (value < 0 || value >= Definitions.RAM_SIZE)
throw new VariableException(value +
" is an illegal value for", varName);
}
// Checks that the given value is a legal 16-bit address
private void check_rom_address(String varName, int value) throws VariableException {
if (value < 0 || value >= Definitions.ROM_SIZE)
throw new VariableException(value +
" is an illegal value for", varName);
}
}