/* DrMIPS - Educational MIPS simulator Copyright (C) 2013-2015 Bruno Nova <[email protected]> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package brunonova.drmips.simulator; import brunonova.drmips.simulator.components.*; import brunonova.drmips.simulator.exceptions.*; import brunonova.drmips.simulator.util.Dimension; import brunonova.drmips.simulator.util.Point; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; /** * Class that represents and manipulates a simulated MIPS CPU. * * @author Bruno Nova */ public class CPU { /** The path to the CPU files, with the trailing slash. */ public static final String FILENAME_PATH = "cpu/"; /** The file extension of the CPU files. */ public static final String FILENAME_EXTENSION = "cpu"; /** The regular expression to validate register names. */ public static final String REGNAME_REGEX = "^[a-zA-Z][a-zA-Z\\d]*$"; /** The prefix char of the registers. */ public static final char REGISTER_PREFIX = '$'; /** The extra margin added to the width of the graphical CPU's size. */ public static final int RIGHT_MARGIN = 10; /** The extra margin added to the height of the graphical CPU's size. */ public static final int BOTTOM_MARGIN = 10; /** The unit used in latencies. */ public static final String LATENCY_UNIT = "ps"; /** The exponent (e), that multiplied by 10 and the latency gives the real latency in seconds (ps * 10 ^ e). */ public static final int LATENCY_EXPONENT = -12; /** The number of clock cycles executed in <tt>executeAll()</tt> after which it throws an exception. */ public static final int EXECUTE_ALL_LIMIT_CYCLES = 1000; /** The file of the CPU. */ private File file = null; /** The components that the CPU contains. */ private Map<String, Component> components; /** The components that are synchronous (convenience list). */ private List<Component> synchronousComponents; /** The names of the registers (without the prefix). */ private List<String> registerNames = null; /** The loaded instruction set. */ private InstructionSet instructionSet = null; /** The assembler for this CPU. */ private Assembler assembler = null; /** The Program Counter (set automatically in <tt>addComponent()</tt>. */ private PC pc = null; /** The register bank (set automatically in <tt>addComponent()</tt>. */ private RegBank regbank = null; /** The instruction memory (set automatically in <tt>addComponent()</tt>. */ private InstructionMemory instructionMemory = null; /** The control unit (set automatically in <tt>addComponent()</tt>. */ private ControlUnit controlUnit = null; /** The ALU controller (set automatically in <tt>addComponent()</tt>. */ private ALUControl aluControl = null; /** The ALU (set automatically in <tt>addComponent()</tt>. */ private ALU alu = null; /** The data memory (set automatically in <tt>addComponent()</tt>. */ private DataMemory dataMemory = null; /** The forwarding unit (set automatically in <tt>addComponent()</tt>. */ private ForwardingUnit forwardingUnit = null; /** The hazard detection unit (set automatically in <tt>addComponent()</tt>. */ private HazardDetectionUnit hazardDetectionUnit = null; /** The IF/ID register, if the CPU is pipelined. */ private PipelineRegister ifIdReg = null; /** The ID/EX register, if the CPU is pipelined. */ private PipelineRegister idExReg = null; /** The EX/MEM register, if the CPU is pipelined. */ private PipelineRegister exMemReg = null; /** The MEM/WB register, if the CPU is pipelined. */ private PipelineRegister memWbReg = null; /** Clock period in LATENCY_UNIT unit. */ private int clockPeriod; /** Clock frequency in Hz. */ private double clockFrequency; /** Number of executed cycles. */ private int executedCycles = 0; /** Number of executed instructions. */ private int executedInstructions = 0; /** Number of forwards. */ private int forwards = 0; /** Number of stalls. */ private int stalls = 0; /** Whether the latencies and critical path should depend on the current instruction. */ private boolean performanceInstructionDependent = false; /** Breakpoint addr . */ private int breakpointAddr = -1; /** * Constructor that should by called by other constructors. */ protected CPU() { components = new TreeMap<>(); synchronousComponents = new LinkedList<>(); assembler = new Assembler(this); } /** * Constructor that should by called by other constructors. * @param file The file of the CPU. */ protected CPU(File file) { this(); setFile(file); } /** * Creates a CPU from a JSON file. * <p><b>Don't forget to call <tt>setPerformanceInstructionDependent()</tt> on the CPU!</b></p>. * @param path Path to the JSON file. * @return CPU created from the file. * @throws IOException If the file doesn't exist or an I/O error occurs. * @throws JSONException If the JSON file is malformed. * @throws InvalidCPUException If the CPU is invalid or incomplete * @throws ArrayIndexOutOfBoundsException If an array index is invalid somewhere (like an invalid register). * @throws InvalidInstructionSetException If the instruction set is invalid. * @throws NumberFormatException If an opcode is not a number. */ public static CPU createFromJSONFile(String path) throws IOException, JSONException, InvalidCPUException, ArrayIndexOutOfBoundsException, InvalidInstructionSetException, NumberFormatException { CPU cpu = new CPU(new File(path)); BufferedReader reader = null; String file = "", line, parentPath = "."; // Read file to String try { File f = new File(path); parentPath = f.getParentFile().getAbsolutePath(); reader = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF8")); while((line = reader.readLine()) != null) file += line + "\n"; reader.close(); } catch(IOException e) { throw e; } finally { if(reader != null) reader.close(); } // Parse the JSON file JSONObject json = new JSONObject(file); parseJSONComponents(cpu, json.getJSONObject("components"), parentPath); cpu.checkRequiredComponents(); if(cpu.hasForwardingUnit()) cpu.forwardingUnit.setRegbank(cpu.getRegBank()); if(cpu.hasHazardDetectionUnit()) cpu.hazardDetectionUnit.setRegbank(cpu.getRegBank()); if(json.has("reg_names")) parseJSONRegNames(cpu, json.getJSONArray("reg_names")); cpu.instructionSet = new InstructionSet(parentPath + File.separator + json.getString("instructions")); cpu.controlUnit.setControl(cpu.getInstructionSet().getControl(), cpu.getInstructionSet().getOpCodeSize()); if(cpu.hasALUControl()) cpu.aluControl.setControlALU(cpu.getInstructionSet().getControlALU()); if(cpu.hasALU()) cpu.alu.setControlALU(cpu.getInstructionSet().getControlALU()); parseJSONWires(cpu, json.getJSONArray("wires")); cpu.determineControlPath(); for(Component c: cpu.getComponents()) // "execute" all components (initialize all outputs/inputs) c.execute(); cpu.calculatePerformance(); return cpu; } private void checkRequiredComponents() throws InvalidCPUException { if(pc == null) throw new InvalidCPUException("The program counter is required!"); if(regbank == null) throw new InvalidCPUException("The register bank is required!"); if(instructionMemory == null) throw new InvalidCPUException("The instruction memory is required!"); if(controlUnit == null) throw new InvalidCPUException("The control unit is required!"); // Check number of pipeline registers (must be 0 or 4) int count = 0; for(Component c: getComponents()) if(c instanceof PipelineRegister) count++; if(count > 0 && count != 4) throw new InvalidCPUException("Pipelined CPUs must have exactly 4 pipeline registers (5 stages)!"); } /** * Calculates the latency in each component and input and determines the critical path of the CPU and of the instruction (if instruction dependent). */ public final void calculatePerformance() { // CPU performance calculateAccumulatedLatencies(false); determineClockPeriodAndFrequency(); if(isPerformanceInstructionDependent()) // instruction performance? calculateAccumulatedLatencies(true); determineCriticalPath(); } /** * Calculates the latency in each component and input and determines the critical path of the instruction. */ protected final void calculateInstructionPerformance() { if(isPerformanceInstructionDependent()) { calculateAccumulatedLatencies(true); determineCriticalPath(); } } /** * Calculates the accumulated latencies of all components. * @param instructionDependent If <tt>true</tt>, the latencies will depend on the current instruction. */ protected final void calculateAccumulatedLatencies(boolean instructionDependent) { for(Component c: getComponents()) // reset latencies and critical path c.resetPerformance(); for(Component c: synchronousComponents) // calculate latencies c.updateAccumulatedLatency(instructionDependent); } /** * Resets the latencies of all the components to their original latencies. */ public final void resetLatencies() { for(Component c: getComponents()) c.resetLatency(); calculatePerformance(); } /** * Sets the latencies of all components to 0 (zero). */ public final void removeLatencies() { for(Component c: getComponents()) c.setLatency(0); calculatePerformance(); } /** * Returns the highest accumulated latency. * @return Highest accumulated latency. */ private int findHighestAccumulatedLatency() { int maxLatency = 0; for(Component c: getComponents()) { if(c.getAccumulatedLatency() > maxLatency) maxLatency = c.getAccumulatedLatency(); for(Input i: c.getInputs()) { if(i.getAccumulatedLatency() > maxLatency) maxLatency = i.getAccumulatedLatency(); } } return maxLatency; } /** * Returns the input(s) with the highest accumulated latency. * @param instructionDependent Whether the result should depend on the current instruction- * @return Input(s) with the highest accumulated latency. */ private List<Input> findHighetsAccumulatedLatencyInputs(boolean instructionDependent) { List<Input> maxIns = new LinkedList<>(); int maxLatency = -1; Collection<Component> comps = isPerformanceInstructionDependent() ? synchronousComponents : components.values(); for(Component c: comps) { if(!isPerformanceInstructionDependent() || ((Synchronous)c).isWritingState()) { for(Input in: c.getInputs()) { if(in.isConnected()) { if(in.getAccumulatedLatency() > maxLatency) { maxIns.clear(); maxIns.add(in); maxLatency = in.getAccumulatedLatency(); } else if(in.getAccumulatedLatency() == maxLatency) maxIns.add(in); } } } } if(maxIns.isEmpty() && instructionDependent) // no inputs for instruction? Fallback to use all inputs return findHighetsAccumulatedLatencyInputs(false); else return maxIns; } /** * Determines the clock period and frequency, setting the respective variables. */ private void determineClockPeriodAndFrequency() { clockPeriod = findHighestAccumulatedLatency(); if(clockPeriod > 0) clockFrequency = 1.0 / (clockPeriod * Math.pow(10, LATENCY_EXPONENT)); else clockFrequency = 0; } /** * Returns the clock period in the LATENCY_UNIT unit. * @return Clock period of the CPU. */ public int getClockPeriod() { return clockPeriod; } /** * Returns the clock frequency in Hz. * @return Clock frequency in Hz. */ public double getClockFrequencyInHz() { return clockFrequency; } /** * Returns the clock frequency in MHz. * @return Clock frequency in MHz. */ public double getClockFrequencyInMHz() { return clockFrequency / Math.pow(10, 6); } /** * Returns the clock frequency in GHz. * @return Clock frequency in GHz. */ public double getClockFrequencyInGHz() { return clockFrequency / Math.pow(10, 9); } /** * Returns the clock frequency as string in an adequate unit. * @return Clock frequency as string with adequate unit. */ public String getClockFrequencyInAdequateUnit() { double mhz, ghz; if((ghz = getClockFrequencyInGHz()) >= 1.0) return String.format("%.2f", ghz) + " GHz"; else if((mhz = getClockFrequencyInMHz()) >= 1.0) return String.format("%.2f", mhz) + " MHz"; else return String.format("%.2f", getClockFrequencyInHz()) + " Hz"; } /** * Returns the number of executed clock cycles. * @return Number of executed cycles. */ public int getNumberOfExecutedCycles() { return executedCycles; } /** * Returns the number of executed instructions. * @return Number of executed instructions. */ public int getNumberOfExecutedInstructions() { return executedInstructions; } /** * Returns the CPI. * @return Cycles Per Instruction. */ public double getCPI() { if(getNumberOfExecutedInstructions() > 0) return (double)getNumberOfExecutedCycles() / (double)getNumberOfExecutedInstructions(); else return 0.0; } /** * Returns the CPI as a formatted string. * @return CPI as string. */ public String getCPIAsString() { return String.format("%.2f", getCPI()); } /** * Returns the amount of time spent executing the program. * @return Execution time (in LATENCY_UNIT unit). */ public long getExecutionTime() { return (long)getNumberOfExecutedCycles() * (long)getClockPeriod(); } /** * Returns the number of forwards. * @return Number of forwards. */ public int getNumberOfForwards() { return forwards; } /** * Returns the number of stalls. * @return Number of stalls. */ public int getNumberOfStalls() { return stalls; } /** * Returns whether the latencies and critical path depend on the current instruction. * @return <tt>true</tt> if the performance depends on the current instruction. */ public boolean isPerformanceInstructionDependent() { return performanceInstructionDependent; } /** * Resets the statistics to zero. */ protected void resetStatistics() { executedCycles = 0; executedInstructions = 0; forwards = 0; stalls = 0; } /** * Determines the CPU's critical path */ private void determineCriticalPath() { List<Input> maxIns = findHighetsAccumulatedLatencyInputs(isPerformanceInstructionDependent()); if(!maxIns.isEmpty()) { for(Input in: maxIns) { in.getConnectedOutput().setInCriticalPath(true); determineCriticalPath(in.getConnectedOutput().getComponent()); } } } /** * Determines the critical path up to the specified component (recursively). * @param c The component. */ private void determineCriticalPath(Component component) { int lat = component.getAccumulatedLatency() - component.getLatency(); for(Input i: component.getInputs()) { if(i.canChangeComponentAccumulatedLatency() && i.getAccumulatedLatency() == lat && i.isConnected() && !i.getConnectedOutput().isInCriticalPath()) { i.getConnectedOutput().setInCriticalPath(true); determineCriticalPath(i.getConnectedOutput().getComponent()); } } } /** * Sets whether the latencies and critical path should depend on the current instruction. * <p><b>This method should be called after loading a CPU.</b></p> * @param instructionDependent Whether the performance should depend on the current instruction. */ public void setPerformanceInstructionDependent(boolean instructionDependent) { if(performanceInstructionDependent != instructionDependent) { performanceInstructionDependent = instructionDependent; calculateAccumulatedLatencies(performanceInstructionDependent); determineCriticalPath(); } } /** * Updates the list of components and wires that are in the control path. */ public final void determineControlPath() { controlUnit.setInControlPath(); if(alu != null) alu.getZero().setInControlPath(); if(aluControl != null) aluControl.setInControlPath(); if(forwardingUnit != null) forwardingUnit.setInControlPath(); if(hazardDetectionUnit != null) hazardDetectionUnit.setInControlPath(); } /** * Returns whether the CPU is pipelined. * @return <tt>True</tt> if the CPU is pipelined. */ public boolean isPipeline() { return ifIdReg != null; } /** * Sets the file of the CPU. * @param file The file. */ public final void setFile(File file) { this.file = file; } /** * Returns the file of the CPU. * @return The file of the CPU. */ public final File getFile() { return file; } /** * Returns the graphical size of the CPU. * <p>The size is calculated here, so avoid calling this method repeteadly!</p> * @return Size of the graphical CPU. */ public Dimension getSize() { int x, y, width, height; width = height = 0; for(Component c: getComponents()) { // check each component's position + size and output wires points // Component's position + size x = c.getPosition().x + c.getSize().width; y = c.getPosition().y + c.getSize().height; if(x > width) width = x; if(y > height) height = y; // Component's outputs points for(Output o: c.getOutputs()) { if(o.isConnected()) { o.getPosition(); if(o.getPosition() != null) { // start point x = o.getPosition().x; y = o.getPosition().y; if(x > width) width = x; if(y > height) height = y; } if(o.getConnectedInput().getPosition() != null) { // end point x = o.getConnectedInput().getPosition().x; y = o.getConnectedInput().getPosition().y; if(x > width) width = x; if(y > height) height = y; } for(Point p: o.getIntermediatePoints()) { // intermediate points if(p.x > width) width = p.x; if(p.y > height) height = p.y; } } } } return new Dimension(width + RIGHT_MARGIN, height + BOTTOM_MARGIN); } /** * Assembles the given code and updates the CPU's instruction and data memory- * @param code The code to assemble. * @throws SyntaxErrorException If the code has a syntax error. */ public void assembleCode(String code) throws SyntaxErrorException { getAssembler().assembleCode(code); } /** * Loads the given assembled instructions into the instruction memory and * starts the simulation. * @param instructions Program's list of assembled instructions. */ protected void loadProgram(List<AssembledInstruction> instructions) { getInstructionMemory().setInstructions(instructions); // load instructions to memory clearPreviousCycles(); // clear all components' saved states setPCAddress(0); // reset PC if(isPipeline()) { // clears the current instruction index in the pipeline registers getIfIdReg().setCurrentInstructionIndex(-1); getIdExReg().setCurrentInstructionIndex(-1); getExMemReg().setCurrentInstructionIndex(-1); getMemWbReg().setCurrentInstructionIndex(-1); } resetStatistics(); calculateInstructionPerformance(); // Refresh critical path } /** * Returns whether the currently loaded program has finished executing. * @return <tt>True</tt> it the program has finished. */ public boolean isProgramFinished() { if(isPipeline()) return pc.getCurrentInstructionIndex() == -1 && ifIdReg.getCurrentInstructionIndex() == -1 && idExReg.getCurrentInstructionIndex() == -1 && exMemReg.getCurrentInstructionIndex() == -1 && memWbReg.getCurrentInstructionIndex() == -1; else return pc.getCurrentInstructionIndex() == -1; } /** * Executes the currently loaded program until the end. * Or until we hit the breakpoint * @throws InfiniteLoopException If the <tt>EXECUTE_ALL_LIMIT_CYCLES</tt> limit has been reached (possible infinite loop). */ public void executeAll() throws InfiniteLoopException { int cycles = 0; while(!isProgramFinished()) { if(cycles++ > EXECUTE_ALL_LIMIT_CYCLES) // prevent possible infinite cycles throw new InfiniteLoopException(); executeCycle(); // check if we have hit the breakpoint if (getPC().getAddress().getValue() == breakpointAddr) { break; } } } /** * Sets the breakpoint address. */ public void setBreakpointAddr(int addr) { breakpointAddr = addr; } /** * "Executes" a clock cycle (a step). */ public void executeCycle() { executedCycles++; if(!isPipeline() || memWbReg.getCurrentInstructionIndex() >= 0) executedInstructions++; if(hasForwardingUnit()) { if(getForwardingUnit().getForwardA().getValue() != 0) forwards++; if(getForwardingUnit().getForwardB().getValue() != 0) forwards++; } if(hasHazardDetectionUnit() && getHazardDetectionUnit().getStall().getValue() != 0) stalls++; saveCycleState(); for(Component c: synchronousComponents) // execute synchronous actions without propagating output changes ((Synchronous)c).executeSynchronous(); // Store index(es) of the instruction(s) being executed int index = getPC().getAddress().getValue() / (Data.DATA_SIZE / 8); if(index < 0 || index >= getInstructionMemory().getNumberOfInstructions()) index = -1; if(isPipeline()) { // save other instructions in pipeline updatePipelineRegisterCurrentInstruction(memWbReg, exMemReg.getCurrentInstructionIndex()); updatePipelineRegisterCurrentInstruction(exMemReg, idExReg.getCurrentInstructionIndex()); updatePipelineRegisterCurrentInstruction(idExReg, ifIdReg.getCurrentInstructionIndex()); updatePipelineRegisterCurrentInstruction(ifIdReg, pc.getCurrentInstructionIndex()); } getPC().setCurrentInstructionIndex(index); for(Component c: synchronousComponents) // execute normal actions, propagating output changes c.execute(); for(Component c: getComponents()) // "execute" all components, just to be safe c.execute(); calculateInstructionPerformance(); // Refresh critical path } /** * Updates the current instruction index stored in the specified pipeline register. * @param reg The pipeline register to update. * @param previousIndex The index of the instruction in the previous stage. */ private void updatePipelineRegisterCurrentInstruction(PipelineRegister reg, int previousIndex) { if(reg.getFlush().getValue() == 1) reg.setCurrentInstructionIndex(-1); else if(reg.getWrite().getValue() == 1) reg.setCurrentInstructionIndex(previousIndex); } /** * Updates the program counter to a new address. * <p>This method also changes the current instruction index to a correct value, * so call this method instead of <tt>getPC().setAddress()</tt>!</p> * @param address The new address. */ public void setPCAddress(int address) { getPC().setAddress(address); // reset PC int index = address / (Data.DATA_SIZE / 8); if(index >= 0 && index < getInstructionMemory().getNumberOfInstructions()) getPC().setCurrentInstructionIndex(index); else getPC().setCurrentInstructionIndex(-1); } /** * Saves the state of the current cycle. */ public void saveCycleState() { for(Component c: synchronousComponents) ((Synchronous)c).pushState(); } /** * Performs a "step back" in the execution if possible (if <tt>hasPreviousCycle() == true</tt>). */ public void restorePreviousCycle() { if(hasPreviousCycle()) { for(Component c: synchronousComponents) // restore previous states ((Synchronous)c).popState(); for(Component c: synchronousComponents) // execute normal actions, propagating output changes c.execute(); for(Component c: getComponents()) // "execute" all components c.execute(); executedCycles--; if(!isPipeline() || memWbReg.getCurrentInstructionIndex() >= 0) executedInstructions--; if(hasForwardingUnit()) { if(getForwardingUnit().getForwardA().getValue() != 0) forwards--; if(getForwardingUnit().getForwardB().getValue() != 0) forwards--; } if(hasHazardDetectionUnit() && getHazardDetectionUnit().getStall().getValue() != 0) stalls--; calculateInstructionPerformance(); // Refresh critical path } } /** * Returns whether there was a previous cycle executed. * @return <tt>True</tt> if a "step back" is possible (<tt>getPc().hasSavedStates() == true</tt>). */ public boolean hasPreviousCycle() { if(pc != null) return pc.hasSavedStates(); else return false; } /** * Removes all the saved previous cycles. */ public void clearPreviousCycles() { for(Component c: synchronousComponents) ((Synchronous)c).clearSavedStates(); } /** * Resets the states of the CPU's components to the first cycle. */ public void resetToFirstCycle() { if(hasPreviousCycle()) { for(Component c: synchronousComponents) // restore first state ((Synchronous)c).resetFirstState(); for(Component c: synchronousComponents) // execute normal actions, propagating output changes c.execute(); for(Component c: getComponents()) // "execute" all components c.execute(); resetStatistics(); calculateInstructionPerformance(); // Refresh critical path } } /** * Resets the stored data of the CPU to zeros (register bank and data memory). */ public void resetData() { regbank.reset(); if(hasDataMemory()) dataMemory.reset(); if(hasALU() && alu instanceof ExtendedALU) ((ExtendedALU)alu).reset(); } /** * Connects the given output to the given input. * @param output Output to connect from. * @param input Input to connect to. * @return The resulting output. * @throws InvalidCPUException If the output or the input are already connected or have different sizes. */ protected Output connectComponents(Output output, Input input) throws InvalidCPUException { output.connectTo(input); return output; } /** * Connects the given output to the given input. * @param outCompId The identifier of the output component. * @param outId The identifier of the output of the output component. * @param inCompId The identifier of the input component. * @param inId The identifier of the input of the input component. * @return The resulting output. * @throws InvalidCPUException If the output or the input are already connected or have different sizes or don't exist. */ protected Output connectComponents(String outCompId, String outId, String inCompId, String inId) throws InvalidCPUException { Component out = getComponent(outCompId); Component in = getComponent(inCompId); if(out == null) throw new InvalidCPUException("Unknown ID " + outCompId + "!"); if(in == null) throw new InvalidCPUException("Unknown ID " + inCompId + "!"); Output o = out.getOutput(outId); Input i = in.getInput(inId); if(o == null) throw new InvalidCPUException("Unknown ID " + outId + "!"); if(i == null) throw new InvalidCPUException("Unknown ID " + inId + "!"); o.connectTo(i); return o; } /** * Returns whether a component with the specified identifier exists. * @param id Component identifier. * @return <tt>true</tt> if the component exists. */ public final boolean hasComponent(String id) { return components.containsKey(id); } /** * Returns the component with the specified identifier. * @param id Component identifier. * @return The desired component, or <tt>null</tt> if it doesn't exist. */ public final Component getComponent(String id) { return components.get(id); } /** * Returns an array with all the coponents. * @return Array with all components. */ public Component[] getComponents() { Component[] c = new Component[components.size()]; return components.values().toArray(c); } /** * Adds the specified component to the CPU. * @param component The component to add. * @throws InvalidCPUException If the new component makes the CPU invalid. */ protected final void addComponent(Component component) throws InvalidCPUException { if(hasComponent(component.getId())) throw new InvalidCPUException("Duplicated ID " + component.getId() + "!"); components.put(component.getId(), component); if(component instanceof Synchronous) synchronousComponents.add(component); if(component instanceof PC) { if(pc != null) throw new InvalidCPUException("Only one program counter allowed!"); pc = (PC)component; } else if(component instanceof RegBank) { if(regbank != null) throw new InvalidCPUException("Only one register bank allowed!"); regbank = (RegBank)component; } else if(component instanceof InstructionMemory) { if(instructionMemory != null) throw new InvalidCPUException("Only one instruction memory allowed!"); instructionMemory = (InstructionMemory)component; } else if(component instanceof ControlUnit) { if(controlUnit != null) throw new InvalidCPUException("Only one control unit allowed!"); controlUnit = (ControlUnit)component; } else if(component instanceof ALUControl) { if(aluControl != null) throw new InvalidCPUException("Only one ALU control allowed!"); aluControl = (ALUControl)component; } else if(component instanceof ALU) { if(alu != null) throw new InvalidCPUException("Only one ALU allowed!"); alu = (ALU)component; } else if(component instanceof DataMemory) { if(dataMemory != null) throw new InvalidCPUException("Only one data memory allowed!"); dataMemory = (DataMemory)component; } else if(component instanceof ForwardingUnit) { if(forwardingUnit != null) throw new InvalidCPUException("Only one forwarding unit allowed!"); forwardingUnit = (ForwardingUnit)component; } else if(component instanceof HazardDetectionUnit) { if(hazardDetectionUnit != null) throw new InvalidCPUException("Only one hazard detection unit allowed!"); hazardDetectionUnit = (HazardDetectionUnit)component; } else if(component instanceof PipelineRegister) { String id = component.getId().trim().toUpperCase(); switch (id) { case "IF/ID": if(ifIdReg != null) throw new InvalidCPUException("Only one IF/ID pipeline register allowed!"); ifIdReg = (PipelineRegister)component; break; case "ID/EX": if(idExReg != null) throw new InvalidCPUException("Only one ID/EX pipeline register allowed!"); idExReg = (PipelineRegister)component; break; case "EX/MEM": if(exMemReg != null) throw new InvalidCPUException("Only one EX/MEM pipeline register allowed!"); exMemReg = (PipelineRegister)component; break; case "MEM/WB": if(memWbReg != null) throw new InvalidCPUException("Only one MEM/WB pipeline register allowed!"); memWbReg = (PipelineRegister)component; break; default: throw new InvalidCPUException("A pipeline register's identifier must be one of {IF/ID, ID/EX, EX/MEM, MEM/WB}!"); } } } /** * Returns the loaded instruction set. * @return Loaded instruction set. */ public final InstructionSet getInstructionSet() { return instructionSet; } /** * Returns the Program Counter. * @return Program Counter. */ public final PC getPC() { return pc; } /** * Returns the register bank. * @return Register bank. */ public final RegBank getRegBank() { return regbank; } /** * Returns the instruction memory. * @return Instruction memory. */ public final InstructionMemory getInstructionMemory() { return instructionMemory; } /** * Returns the control unit. * @return Control unit. */ public final ControlUnit getControlUnit() { return controlUnit; } /** * Returns the ALU control. * @return ALU control. */ public final ALUControl getALUControl() { return aluControl; } /** * Returns whether the CPU contains an ALU control. * @return <tt>True</tt> if an ALU control exists. */ public final boolean hasALUControl() { return aluControl != null; } /** * Returns the ALU. * @return ALu. */ public final ALU getALU() { return alu; } /** * Returns whether the CPU contains an ALU. * @return <tt>True</tt> if an ALU exists. */ public final boolean hasALU() { return alu != null; } /** * Returns the data memory. * @return Data memory. */ public final DataMemory getDataMemory() { return dataMemory; } /** * Returns whether the CPU contains data memory. * @return <tt>True</tt> if a data memory exists. */ public final boolean hasDataMemory() { return dataMemory != null; } /** * Returns the forwarding unit. * @return Forwarding unit. */ public final ForwardingUnit getForwardingUnit() { return forwardingUnit; } /** * Returns whether the CPU contains a forwarding unit. * @return <tt>True</tt> if a forwarding unit exists. */ public final boolean hasForwardingUnit() { return forwardingUnit != null; } /** * Returns the hazard detection unit. * @return Hazard detection unit. */ public final HazardDetectionUnit getHazardDetectionUnit() { return hazardDetectionUnit; } /** * Returns whether the CPU contains a hazard detection unit. * @return <tt>True</tt> if a hazard detection unit exists. */ public final boolean hasHazardDetectionUnit() { return hazardDetectionUnit != null; } /** * Returns the IF/ID pipeline register. * @return IF/ID pipeline register, or <tt>null</tt> if not pipeline. */ public final PipelineRegister getIfIdReg() { return ifIdReg; } /** * Returns the ID/EX pipeline register. * @return ID/EX pipeline register, or <tt>null</tt> if not pipeline. */ public final PipelineRegister getIdExReg() { return idExReg; } /** * Returns the EX/MEM pipeline register. * @return EX/MEM pipeline register, or <tt>null</tt> if not pipeline. */ public final PipelineRegister getExMemReg() { return exMemReg; } /** * Returns the MEM/WB pipeline register. * @return MEM/WB pipeline register, or <tt>null</tt> if not pipeline. */ public final PipelineRegister getMemWbReg() { return memWbReg; } /** * Returns the index/address of the register with the specified name. * @param name Name of the register (with prefix). * @return The index of the register, or -1 if it doesn't exist. */ public int getRegisterIndex(String name) { name = name.trim().toLowerCase(); if(name.length() < 2 || name.charAt(0) != REGISTER_PREFIX) return -1; name = name.substring(1); try { int index = Integer.parseInt(name); // Numeric name (like $0) if(index >= 0 && index < regbank.getNumberOfRegisters()) return index; else return -1; } catch(NumberFormatException e) { // Register name (like $zero) if(registerNames != null) return registerNames.indexOf(name); else return -1; } } /** * Returns the name of the register with the specified index/address. * @param index The index of the register. * @return The name of the register (with prefix). * @throws IndexOutOfBoundsException If the register with the given index doesn't exist. */ public String getRegisterName(int index) throws IndexOutOfBoundsException { if(registerNames != null) return REGISTER_PREFIX + registerNames.get(index); else return REGISTER_PREFIX + "" + index; } /** * Returns whether a register with the given name exists. * @param name Name of the register (with prefix). * @return <tt>true</tt> if the register exists. */ public boolean hasRegister(String name) { return getRegisterIndex(name) != -1; } /** * Returns the assembler for this CPU. * @return The assembler for this CPU. */ public Assembler getAssembler() { return assembler; } /** * Parses and creates the components from the given JSON array. * @param cpu The CPU to add the components to. * @param components JSONObject that contains the components array. * @param parentPath Path to the cpu file's parent directory. * @throws JSONException If the JSON file is malformed. * @throws InvalidCPUException If the CPU is invalid or incomplete. */ private static void parseJSONComponents(CPU cpu, JSONObject components, String parentPath) throws JSONException, InvalidCPUException { JSONObject json; String type, id; Class cl; Component comp; // ClassLoader to load the built-in components ClassLoader loader = CPU.class.getClassLoader(); // ClassLoader to load custom components File parentDir = new File(parentPath + File.separator); ClassLoader customLoader; try { URL[] urls = new URL[] {parentDir.toURI().toURL()}; customLoader = new URLClassLoader(urls); } catch(Exception ex) { customLoader = null; } // Parse the components Iterator<String> i = components.keys(); while(i.hasNext()) { id = i.next(); json = components.getJSONObject(id); type = json.getString("type"); // Load the class with the name specified by "type" try { // Search in the built-in classes first cl = loader.loadClass("brunonova.drmips.simulator.components." + type); } catch(ClassNotFoundException ex) { // Search in the custom components second if(customLoader != null) { try { cl = customLoader.loadClass(type); } catch(ClassNotFoundException ex2) { ex2.initCause(ex); throw new InvalidCPUException("Unknown component type " + type + "!", ex2); } } else { throw new InvalidCPUException("Unknown component type " + type + "!", ex); } } // Create the component with the (String, JSONObject) contructor // and add it to the CPU try { comp = (Component)cl.asSubclass(Component.class) .getConstructor(String.class, JSONObject.class) .newInstance(id, json); cpu.addComponent(comp); } catch(ClassCastException ex) { throw new InvalidCPUException("The " + type + " class is not a subclass of Component!", ex); } catch(NoSuchMethodException ex) { throw new InvalidCPUException("The " + type + " class is missing the (String, JSONObject) constructor!", ex); } catch(InvocationTargetException ex) { Throwable target = ex.getCause(); if(target instanceof InvalidCPUException) { throw (InvalidCPUException)target; } else if(target instanceof JSONException) { throw (JSONException)target; } else { throw new InvalidCPUException("Failed to create the component " + id + "!", ex); } } catch(InstantiationException | IllegalAccessException | IllegalArgumentException ex) { throw new InvalidCPUException("Failed to create the component " + id + "!", ex); } } } /** * Parses wires from the given JSON array and connects the components. * @param cpu The CPU to add wires to. * @param wires JSONArray that contains the wires array. * @throws JSONException If the JSON file is malformed. * @throws InvalidCPUException If the output or the input are already connected or have different sizes or doesn't exist. */ private static void parseJSONWires(CPU cpu, JSONArray wires) throws JSONException, InvalidCPUException { JSONObject wire, point, start, end; JSONArray points; Output out; for(int i = 0; i < wires.length(); i++) { wire = wires.getJSONObject(i); out = cpu.connectComponents(wire.getString("from"), wire.getString("out"), wire.getString("to"), wire.getString("in")); points = wire.optJSONArray("points"); if(points != null) { for(int x = 0; x < points.length(); x++) { point = points.getJSONObject(x); out.addIntermediatePoint(new Point(point.getInt("x"), point.getInt("y"))); } } if((start = wire.optJSONObject("start")) != null) out.setPosition(new Point(start.getInt("x"), start.getInt("y"))); if((end = wire.optJSONObject("end")) != null) out.getConnectedInput().setPosition(new Point(end.getInt("x"), end.getInt("y"))); } } /** * Parses and sets the identifiers of the registers. * @param cpu The CPU to set the registers informations. * @param regs JSONArray that contains the registers. * @throws JSONException If the JSON file is malformed. * @throws InvalidCPUException If not all registers are specified or a register is invalid. */ private static void parseJSONRegNames(CPU cpu, JSONArray regs) throws JSONException, InvalidCPUException { if(regs.length() != cpu.getRegBank().getNumberOfRegisters()) throw new InvalidCPUException("Not all registers have been specified in the registers block!"); cpu.registerNames = new ArrayList<>(cpu.getRegBank().getNumberOfRegisters()); String id; for(int i = 0; i < regs.length(); i++) { id = regs.getString(i).trim().toLowerCase(); if(id.isEmpty()) throw new InvalidCPUException("Invalid name " + id + "!"); if(!id.matches(REGNAME_REGEX)) // has only letters and digits and starts with a letter? throw new InvalidCPUException("Invalid name " + id + "!"); if(cpu.hasRegister(REGISTER_PREFIX + id)) throw new InvalidCPUException("Invalid name " + id + "!"); cpu.registerNames.add(id); } } }