/********************************************************************************
* 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.Assembler.AssemblerException;
import Hack.Assembler.HackAssemblerTranslator;
import Hack.ComputerParts.Bus;
import Hack.ComputerParts.Register;
import Hack.Controller.ProgramException;
import Hack.Utilities.Definitions;
/**
* A computer with memory (ROM & RAM) and two registers (A & D). Includes
* a method for loading a program (HACK file) into the ROM and another
* method for running the program which is currently in them ROM. Also gives
* read access to the memory.
*/
public class CPU
{
// The address and program counter registers.
protected PointerAddressRegisterAdapter A, PC;
// The data register
protected Register D;
// The RAM array.
protected RAM M;
// The ROM array
protected ROM rom;
// The ALU
protected ALU alu;
// The bus.
protected Bus bus;
// The time that passed since the program started running.
protected long time;
// An assembler transltor
protected HackAssemblerTranslator assemblerTranslator;
/**
* Constructs a new cpu with the given ROM, RAM, A, D, PC & ALU.
*/
public CPU(RAM ram, ROM rom, PointerAddressRegisterAdapter A, Register D,
PointerAddressRegisterAdapter PC, ALU alu, Bus bus) {
this.M = ram;
this.rom = rom;
this.A = A;
this.D = D;
this.PC = PC;
this.alu = alu;
this.bus = bus;
A.setUpdatePointer(false);
assemblerTranslator = HackAssemblerTranslator.getInstance();
}
/**
* Returns the bus.
*/
public Bus getBus() {
return bus;
}
/**
* Returns the A register.
*/
public Register getA() {
return A;
}
/**
* Returns the D register.
*/
public Register getD() {
return D;
}
/**
* Returns the PC register.
*/
public Register getPC() {
return PC;
}
/**
* Returns the RAM (random access memory).
*/
public RAM getRAM() {
return M;
}
/**
* Returns the ROM (read only memory).
*/
public ROM getROM() {
return rom;
}
/**
* Returns the ALU.
*/
public ALU getALU() {
return alu;
}
/**
* Returns the time that passed since the program started running.
*/
public long getTime() {
return time;
}
/**
* Restarts the program from the beginning.
*/
public void initProgram() {
A.reset();
A.setUpdatePointer(true);
A.setUpdatePointer(false);
D.reset();
PC.reset();
alu.reset();
M.clearScreen();
M.hideSelect();
M.hideHighlight();
rom.hideSelect();
rom.hideHighlight();
time = 0;
}
/**
* Executes the current instruction (ROM at pc).
* Throws ProgramException if the current instruction is illegal or
* if it causes an illegal effect (read/write from M when A is an illegal
* address or jump when A is an illegal address).
*/
public void executeInstruction() throws ProgramException {
short instruction = rom.getValueAt(PC.get());
boolean pcChanged = false;
if ((instruction & 0x8000) == 0)
bus.send(rom, PC.get(), A, 0);
else if ((instruction & 0xe000) == 0xe000) {
computeExp(instruction);
setDestination(instruction);
pcChanged = checkJump(instruction);
}
else if (instruction != HackAssemblerTranslator.NOP)
throw new ProgramException("At line " + PC.get() +
": Illegal instruction");
if (!pcChanged) {
short newPC = (short)(PC.get() + 1);
if (newPC < 0 || newPC >= Definitions.ROM_SIZE)
throw new ProgramException("At line " + PC.get() +
": Can't continue past last line");
PC.setValueAt(0, newPC, true);
}
time++;
}
// computes the exp part of the given instruction.
// The result will be at the alu's output.
// Throws ProgramException if the calculation involves M and A contains
// an illegal address.
protected void computeExp(short instruction) throws ProgramException {
boolean indirect = (instruction & 0x1000) > 0;
boolean zd = (instruction & 0x0800) > 0;
boolean nd = (instruction & 0x0400) > 0;
boolean zm = (instruction & 0x0200) > 0;
boolean nm = (instruction & 0x0100) > 0;
boolean f = (instruction & 0x0080) > 0;
boolean no = (instruction & 0x0040) > 0;
try {
alu.setCommand(assemblerTranslator.getExpByCode((short)(instruction & 0xffc0)),
zd, nd, zm, nm, f, no);
} catch (AssemblerException ae) {}
bus.send(D, 0, alu, 0); // sends D to input0 of the alu
// sends A or M[A] to input1 of the alu
if (indirect) {
int address = A.get();
if (address < 0 || address >= M.getSize())
throw new ProgramException("At line " + PC.get() +
": Expression involves M but A=" +
address +
" is an illegal memory address.");
A.setUpdatePointer(true);
bus.send(M, address, alu, 1);
A.setUpdatePointer(false);
}
else
bus.send(A, 0, alu, 1);
alu.compute();
}
// Sets the registers with the alu's output according to
// the given instruction
// Throws ProgramException if destination contains M and A contains
// an illegal address.
protected void setDestination(short instruction) throws ProgramException {
boolean destA = (instruction & 0x0020) > 0;
boolean destD = (instruction & 0x0010) > 0;
boolean destM = (instruction & 0x0008) > 0;
if (destM) {
int address = A.get();
if (address < 0 || address >= M.getSize())
throw new ProgramException("At line " + PC.get() +
": Destination is M but A=" +
address +
" is an illegal memory address.");
A.setUpdatePointer(true);
bus.send(alu, 2, M, address);
A.setUpdatePointer(false);
}
if (destA)
bus.send(alu, 2, A, 0);
if (destD)
bus.send(alu, 2, D, 0);
}
// Sets the program counter (if necessary) according to
// the given instruction and the alu's output.
// If the program counter was changed, returns true, otherwise false.
// Throws ProgramException if the program counter should be changed and A
// contains an illegal address.
protected boolean checkJump(short instruction) throws ProgramException {
boolean jumpNegative = (instruction & 0x0004) > 0;
boolean jumpEqual = (instruction & 0x0002) > 0;
boolean jumpPositive = (instruction & 0x0001) > 0;
boolean changed = false;
short exp = alu.getValueAt(2);
if ((exp < 0 && jumpNegative) ||
(exp == 0 && jumpEqual) ||
(exp > 0 && jumpPositive)) {
int newPC = A.get();
if (newPC < 0 || newPC >= Definitions.ROM_SIZE)
throw new ProgramException("At line " + PC.get() +
": Jump requested but A=" + newPC +
" is an illegal program address.");
bus.send(A, 0, PC, 0);
changed = true;
}
return changed;
}
}