/******************************************************************************** * 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.Controller.*; import Hack.ComputerParts.*; import Hack.Utilities.*; import Hack.Assembler.*; /** * 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 translator 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); pcChanged = checkJump(instruction); setDestination(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; } }