diff --git a/shift-register.sim b/shift-register.sim new file mode 100644 index 0000000..f6fc721 --- /dev/null +++ b/shift-register.sim @@ -0,0 +1,369 @@ +{ + "version": "1.8.2", + "globalBitSize": 1, + "clockSpeed": 1, + "circuits": [ + { + "name": "main", + "components": [ + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.Tunnel", + "x": 18, + "y": 29, + "properties": { + "Label": "clk", + "Direction": "NORTH", + "Bitsize": "1" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.Tunnel", + "x": 24, + "y": 29, + "properties": { + "Label": "clk", + "Direction": "NORTH", + "Bitsize": "1" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.Tunnel", + "x": 20, + "y": 32, + "properties": { + "Label": "rst", + "Direction": "NORTH", + "Bitsize": "1" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.Tunnel", + "x": 30, + "y": 29, + "properties": { + "Label": "clk", + "Direction": "NORTH", + "Bitsize": "1" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.Tunnel", + "x": 26, + "y": 32, + "properties": { + "Label": "rst", + "Direction": "NORTH", + "Bitsize": "1" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.Tunnel", + "x": 36, + "y": 29, + "properties": { + "Label": "clk", + "Direction": "NORTH", + "Bitsize": "1" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.Tunnel", + "x": 32, + "y": 32, + "properties": { + "Label": "rst", + "Direction": "NORTH", + "Bitsize": "1" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.Tunnel", + "x": 38, + "y": 32, + "properties": { + "Label": "rst", + "Direction": "NORTH", + "Bitsize": "1" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.Tunnel", + "x": 8, + "y": 19, + "properties": { + "Label": "clk", + "Direction": "WEST", + "Bitsize": "1" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.Tunnel", + "x": 8, + "y": 22, + "properties": { + "Label": "rst", + "Direction": "WEST", + "Bitsize": "1" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.Tunnel", + "x": 42, + "y": 25, + "properties": { + "Label": "end", + "Direction": "WEST", + "Bitsize": "16" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.SubcircuitPeer", + "x": 31, + "y": 25, + "properties": { + "Label location": "NORTH", + "Label": "", + "Subcircuit": "gay" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.io.Button", + "x": 6, + "y": 22, + "properties": { + "Label location": "WEST", + "Label": "rst", + "Direction": "EAST" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.memory.RegisterPeer", + "x": 19, + "y": 24, + "properties": { + "Label location": "NORTH", + "Label": "r0", + "Bitsize": "16" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.memory.RegisterPeer", + "x": 25, + "y": 24, + "properties": { + "Label location": "NORTH", + "Label": "r1", + "Bitsize": "16" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.memory.RegisterPeer", + "x": 37, + "y": 24, + "properties": { + "Label location": "NORTH", + "Label": "r3", + "Bitsize": "16" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.ClockPeer", + "x": 6, + "y": 19, + "properties": { + "Label location": "WEST", + "Label": "clk", + "Direction": "EAST" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.PinPeer", + "x": 6, + "y": 25, + "properties": { + "Label location": "WEST", + "Label": "input", + "Is input?": "Yes", + "Direction": "EAST", + "Bitsize": "16" + } + } + ], + "wires": [ + { + "x": 14, + "y": 26, + "length": 5, + "isHorizontal": true + }, + { + "x": 20, + "y": 28, + "length": 1, + "isHorizontal": false + }, + { + "x": 22, + "y": 28, + "length": 4, + "isHorizontal": false + }, + { + "x": 23, + "y": 26, + "length": 2, + "isHorizontal": true + }, + { + "x": 26, + "y": 28, + "length": 1, + "isHorizontal": false + }, + { + "x": 28, + "y": 28, + "length": 4, + "isHorizontal": false + }, + { + "x": 29, + "y": 26, + "length": 2, + "isHorizontal": true + }, + { + "x": 32, + "y": 28, + "length": 1, + "isHorizontal": false + }, + { + "x": 33, + "y": 28, + "length": 1, + "isHorizontal": true + }, + { + "x": 34, + "y": 26, + "length": 3, + "isHorizontal": true + }, + { + "x": 34, + "y": 28, + "length": 4, + "isHorizontal": false + }, + { + "x": 38, + "y": 28, + "length": 1, + "isHorizontal": false + }, + { + "x": 40, + "y": 28, + "length": 4, + "isHorizontal": false + }, + { + "x": 41, + "y": 26, + "length": 1, + "isHorizontal": true + } + ] + }, + { + "name": "gay", + "components": [ + { + "name": "com.ra4king.circuitsim.gui.peers.memory.RegisterPeer", + "x": 31, + "y": 24, + "properties": { + "Label location": "NORTH", + "Label": "r2", + "Bitsize": "16" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.PinPeer", + "x": 33, + "y": 29, + "properties": { + "Label location": "EAST", + "Label": "rst", + "Is input?": "Yes", + "Direction": "NORTH", + "Bitsize": "1" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.PinPeer", + "x": 31, + "y": 29, + "properties": { + "Label location": "WEST", + "Label": "clk", + "Is input?": "Yes", + "Direction": "NORTH", + "Bitsize": "1" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.PinPeer", + "x": 21, + "y": 25, + "properties": { + "Label location": "WEST", + "Label": "d", + "Is input?": "Yes", + "Direction": "EAST", + "Bitsize": "16" + } + }, + { + "name": "com.ra4king.circuitsim.gui.peers.wiring.PinPeer", + "x": 37, + "y": 25, + "properties": { + "Label location": "EAST", + "Label": "q", + "Is input?": "No", + "Direction": "WEST", + "Bitsize": "16" + } + } + ], + "wires": [ + { + "x": 29, + "y": 26, + "length": 2, + "isHorizontal": true + }, + { + "x": 32, + "y": 28, + "length": 1, + "isHorizontal": false + }, + { + "x": 34, + "y": 28, + "length": 1, + "isHorizontal": false + }, + { + "x": 35, + "y": 26, + "length": 2, + "isHorizontal": true + } + ] + } + ] +} diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/api/BaseMemory.java b/src/main/java/edu/gatech/cs2110/circuitsim/api/BaseMemory.java new file mode 100644 index 0000000..d5807f5 --- /dev/null +++ b/src/main/java/edu/gatech/cs2110/circuitsim/api/BaseMemory.java @@ -0,0 +1,46 @@ +package edu.gatech.cs2110.circuitsim.api; + +import java.io.InputStream; +import java.util.Scanner; + +public abstract class BaseMemory { + public abstract void store(int address, int value); + + /** + * DO NOT MODIFY RESULT + */ + public abstract int[] getContents(); + + /** + * Loads a stream of a dat file into this component's memory. Format + * is the same as the .dat files saved in the CircuitSim memory + * editor window. + */ + public void load(InputStream stream) { + Scanner scanner = new Scanner(stream); + + int address = 0; + while (scanner.hasNext()) { + String word = scanner.next(); + + if (word.contains("-")) { + // Roi's world-famous patented run-length encoding + // `x-y', where x is the number of occurrences and y is a + // hex word + String[] pieces = word.split("-"); + if (pieces.length != 2) { + throw new IllegalArgumentException("invalid run-length encoded word " + word); + } + int length = Integer.parseInt(pieces[0]); + int value = Integer.parseInt(pieces[1], 16); + + for (int i = 0; i < length; i++) { + store(address++, value); + } + } else { + int value = Integer.parseInt(word, 16); + store(address++, value); + } + } + } +} diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/api/Bits.java b/src/main/java/edu/gatech/cs2110/circuitsim/api/Bits.java new file mode 100644 index 0000000..0375579 --- /dev/null +++ b/src/main/java/edu/gatech/cs2110/circuitsim/api/Bits.java @@ -0,0 +1,39 @@ +package edu.gatech.cs2110.circuitsim.api; + +import java.util.function.Supplier; + +/** + * Provides useful bit twiddling operations. + */ +class Bits { + /** + * Sign-extends a value. + */ + static int sext(int orig, int bits) { + // x << 32 == x if x is a java int, so exclude that case + if (bits < 32 && (orig & (1 << (bits - 1))) != 0) { + return orig | (-1 << bits); + } else { + return orig; + } + } + + /** + * An IllegalStateException is thrown when at least one bit is floating. + * But it just says "Invalid value," so throw a more human-friendly error + * message for the sake of students' mental stability. + */ + static T betterFloatingErrorMessage(Supplier x) { + try { + return x.get(); + } catch (IllegalStateException err) { + if (err.getMessage().equals("Invalid value")) { + throw new IllegalStateException( + "At least one output bit is floating (undefined, or blue " + + "in CircuitSim). Is the output pin connected to anything?", err); + } else { + throw err; + } + } + } +} diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/api/Button.java b/src/main/java/edu/gatech/cs2110/circuitsim/api/Button.java new file mode 100644 index 0000000..6756094 --- /dev/null +++ b/src/main/java/edu/gatech/cs2110/circuitsim/api/Button.java @@ -0,0 +1,12 @@ +package edu.gatech.cs2110.circuitsim.api; + +// TODO FIXME document +public class Button extends MockPulser { + public Button(InputPin mockPin, Subcircuit subcircuit) { + super(mockPin, subcircuit); + } + + public void press() { + pulse(); + } +} diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/api/Clock.java b/src/main/java/edu/gatech/cs2110/circuitsim/api/Clock.java new file mode 100644 index 0000000..16f8fbd --- /dev/null +++ b/src/main/java/edu/gatech/cs2110/circuitsim/api/Clock.java @@ -0,0 +1,28 @@ +package edu.gatech.cs2110.circuitsim.api; + +import java.util.function.BooleanSupplier; + +// TODO FIXME document +public class Clock extends MockPulser { + public Clock(InputPin mockPin, Subcircuit subcircuit) { + super(mockPin, subcircuit); + } + + public void tick() { + pulse(); + } + + public long tickUntil(long maxCycleCount, BooleanSupplier stopWhen) { + long ticks = 0; + while (!stopWhen.getAsBoolean()) { + if (ticks > maxCycleCount) { + throw new IllegalStateException( + "Ticked clock more than " + maxCycleCount + " times without finishing. " + + "Please check for errors in your logic"); + } + + tick(); + } + return ticks; + } +} diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/api/MockPulser.java b/src/main/java/edu/gatech/cs2110/circuitsim/api/MockPulser.java new file mode 100644 index 0000000..3e38fa1 --- /dev/null +++ b/src/main/java/edu/gatech/cs2110/circuitsim/api/MockPulser.java @@ -0,0 +1,26 @@ +package edu.gatech.cs2110.circuitsim.api; + +// TODO FIXME document +public abstract class MockPulser { + protected InputPin mockPin; + protected Subcircuit subcircuit; + + public MockPulser(InputPin mockPin, Subcircuit subcircuit) { + this.mockPin = mockPin; + this.subcircuit = subcircuit; + } + + public InputPin getMockPin() { + return mockPin; + } + + public Subcircuit getSubcircuit() { + return subcircuit; + } + + protected void pulse() { + mockPin.set(0b0); + mockPin.set(0b1); + mockPin.set(0b0); + } +} diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/api/OutputPin.java b/src/main/java/edu/gatech/cs2110/circuitsim/api/OutputPin.java index 45ee4c8..02018d0 100644 --- a/src/main/java/edu/gatech/cs2110/circuitsim/api/OutputPin.java +++ b/src/main/java/edu/gatech/cs2110/circuitsim/api/OutputPin.java @@ -28,16 +28,9 @@ public OutputPin(Pin pin, Subcircuit subcircuit) { * @see getSext */ public int get() { - try { - return subcircuit.getCircuitState().getLastReceived(pin.getPort(Pin.PORT)).getValue(); - } catch (IllegalStateException err) { - // An IllegalStateException is thrown when at least one bit - // is floating. But it just says "Invalid value," so throw a - // more human-friendly error message - throw new IllegalStateException( - "At least one output bit is floating (undefined, or blue in CircuitSim). " + - "Is the output pin connected to anything?"); - } + return Bits.betterFloatingErrorMessage(() -> + subcircuit.getCircuitState().getLastReceived(pin.getPort(Pin.PORT)) + .getValue()); } /** @@ -49,14 +42,6 @@ public int get() { * @see get */ public int getSext() { - int got = get(); - int bits = pin.getBitSize(); - - // x << 32 == x if x is a java int, so exclude that case - if (bits < 32 && (got & (1 << (bits - 1))) != 0) { - return got | (-1 << bits); - } else { - return got; - } + return Bits.sext(get(), pin.getBitSize()); } } diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/api/Ram.java b/src/main/java/edu/gatech/cs2110/circuitsim/api/Ram.java new file mode 100644 index 0000000..b5395fc --- /dev/null +++ b/src/main/java/edu/gatech/cs2110/circuitsim/api/Ram.java @@ -0,0 +1,62 @@ +package edu.gatech.cs2110.circuitsim.api; + +import com.ra4king.circuitsim.simulator.components.memory.RAM; + +/** + * Wraps a CircuitSim RAM component. + * + * @author Austin Adams + */ +public class Ram extends BaseMemory { + private RAM ram; + private Subcircuit subcircuit; + + /** + * Creates a new Ram which wraps the provided {@code RAM} + * component and which lives in the provided {@code Subcircuit}. + * + * @param ram {@code RAM} component to wrap + * @param subcircuit where this pin lives + */ + public Ram(RAM ram, Subcircuit subcircuit) { + this.ram = ram; + this.subcircuit = subcircuit; + } + + @Override + public void store(int address, int value) { + ram.store(subcircuit.getCircuitState(), address, value); + } + + /** + * DO NOT MODIFY RESULT + */ + @Override + public int[] getContents() { + return ram.getMemoryContents(subcircuit.getCircuitState()); + } + + /** + * Returns the internal CircuitSim {@code RAM} component this + * object wraps. + *

+ * This exposes an internal CircuitSim API. Do not use unless you + * know what you are doing. + * + * @return the CircuitSim {@code RAM} component wrapped by this + * object. + */ + public RAM getRAM() { + return ram; + } + + /** + * Returns the {@link Subcircuit}, a wrapper around a CircuitSim + * {@code CircuitBoard} where this Pin lives. + * + * @return the {@link Subcircuit} where this pin lives + */ + public Subcircuit getSubcircuit() { + return subcircuit; + } +} diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/api/Register.java b/src/main/java/edu/gatech/cs2110/circuitsim/api/Register.java new file mode 100644 index 0000000..fa915ec --- /dev/null +++ b/src/main/java/edu/gatech/cs2110/circuitsim/api/Register.java @@ -0,0 +1,73 @@ +package edu.gatech.cs2110.circuitsim.api; + +import static com.ra4king.circuitsim.simulator.components.memory.Register.PORT_OUT; + +/** + * Wraps a CircuitSim Register component. Currently changing the value + * of the register is not supported, but you can observe its value. + * + * @author Austin Adams + */ +public class Register { + private com.ra4king.circuitsim.simulator.components.memory.Register reg; + private Subcircuit subcircuit; + + /** + * Creates a new Register which wraps the provided {@code Register} + * component and which lives in the provided {@code Subcircuit}. + * + * @param reg {@code Register} component to wrap + * @param subcircuit where this pin lives + */ + public Register(com.ra4king.circuitsim.simulator.components.memory.Register reg, + Subcircuit subcircuit) { + this.reg = reg; + this.subcircuit = subcircuit; + } + + /** + * Returns the current value of the register. This value is not sign + * extended. + */ + public int getQ() { + return Bits.betterFloatingErrorMessage(() -> + subcircuit.getCircuitState().getLastPushed(reg.getPort(PORT_OUT)).getValue()); + } + + /** + * Returns the current value of the register, sign-extended to 32 + * bits. Like {@code getQ()} except sign-extended. + */ + public int getQSext() { + return Bits.sext(getQ(), reg.getBitSize()); + } + + /** + * Returns the internal CircuitSim {@code Register} component this + * object wraps. + *

+ * This exposes an internal CircuitSim API. Do not use unless you + * know what you are doing. + * + * @return the CircuitSim {@code Register} component wrapped by this + * object. + */ + public com.ra4king.circuitsim.simulator.components.memory.Register getRegister() { + return reg; + } + + // TODO FIXME document + public MockRegister mock() { + return subcircuit.mockRegister(reg); + } + + /** + * Returns the {@link Subcircuit}, a wrapper around a CircuitSim + * {@code CircuitBoard} where this Register lives. + * + * @return the {@link Subcircuit} where this pin lives + */ + public Subcircuit getSubcircuit() { + return subcircuit; + } +} diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/api/Rom.java b/src/main/java/edu/gatech/cs2110/circuitsim/api/Rom.java new file mode 100644 index 0000000..b2ba060 --- /dev/null +++ b/src/main/java/edu/gatech/cs2110/circuitsim/api/Rom.java @@ -0,0 +1,63 @@ +package edu.gatech.cs2110.circuitsim.api; + +import com.ra4king.circuitsim.simulator.components.memory.ROM; + +/** + * Wraps a CircuitSim RAM component. + * + * @author Austin Adams + */ +public class Rom extends BaseMemory { + private ROM rom; + private Subcircuit subcircuit; + + /** + * Creates a new Ram which wraps the provided {@code RAM} + * component and which lives in the provided {@code Subcircuit}. + * + * @param rom {@code RAM} component to wrap + * @param subcircuit where this pin lives + */ + public Rom(ROM rom, Subcircuit subcircuit) { + this.rom = rom; + this.subcircuit = subcircuit; + } + + @Override + public void store(int address, int value) { + rom.getMemory()[address] = value; + subcircuit.getCircuit().forEachState(state -> rom.valueChanged(state, null, 0)); + } + + /** + * DO NOT MODIFY RESULT + */ + @Override + public int[] getContents() { + return rom.getMemory(); + } + + /** + * Returns the internal CircuitSim {@code RAM} component this + * object wraps. + *

+ * This exposes an internal CircuitSim API. Do not use unless you + * know what you are doing. + * + * @return the CircuitSim {@code RAM} component wrapped by this + * object. + */ + public ROM getROM() { + return rom; + } + + /** + * Returns the {@link Subcircuit}, a wrapper around a CircuitSim + * {@code CircuitBoard} where this Pin lives. + * + * @return the {@link Subcircuit} where this pin lives + */ + public Subcircuit getSubcircuit() { + return subcircuit; + } +} diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/api/Subcircuit.java b/src/main/java/edu/gatech/cs2110/circuitsim/api/Subcircuit.java index 130e6e9..0e9391b 100644 --- a/src/main/java/edu/gatech/cs2110/circuitsim/api/Subcircuit.java +++ b/src/main/java/edu/gatech/cs2110/circuitsim/api/Subcircuit.java @@ -1,33 +1,31 @@ package edu.gatech.cs2110.circuitsim.api; +import static com.ra4king.circuitsim.simulator.components.memory.Register.PORT_IN; +import static com.ra4king.circuitsim.simulator.components.memory.Register.PORT_ENABLE; +import static com.ra4king.circuitsim.simulator.components.memory.Register.PORT_CLK; +import static com.ra4king.circuitsim.simulator.components.memory.Register.PORT_ZERO; +import static com.ra4king.circuitsim.simulator.components.memory.Register.PORT_OUT; + import java.io.File; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.function.BiConsumer; import java.util.stream.Collectors; -import javafx.scene.canvas.Canvas; + +import com.ra4king.circuitsim.gui.*; +import com.ra4king.circuitsim.gui.Properties; +import com.ra4king.circuitsim.gui.peers.memory.RAMPeer; +import com.ra4king.circuitsim.gui.peers.memory.ROMPeer; +import com.ra4king.circuitsim.gui.peers.wiring.ClockPeer; +import com.ra4king.circuitsim.gui.peers.wiring.Tunnel; +import com.ra4king.circuitsim.simulator.components.memory.RAM; import javafx.util.Pair; -import com.ra4king.circuitsim.gui.CircuitBoard; -import com.ra4king.circuitsim.gui.CircuitManager; -import com.ra4king.circuitsim.gui.CircuitSim; -import com.ra4king.circuitsim.gui.ComponentManager; -import com.ra4king.circuitsim.gui.ComponentPeer; -import com.ra4king.circuitsim.gui.GuiUtils; import com.ra4king.circuitsim.gui.peers.SubcircuitPeer; import com.ra4king.circuitsim.gui.peers.memory.RegisterPeer; import com.ra4king.circuitsim.gui.peers.wiring.PinPeer; -import com.ra4king.circuitsim.gui.peers.wiring.PinPeer; -import com.ra4king.circuitsim.gui.Properties; -import com.ra4king.circuitsim.gui.Properties; import com.ra4king.circuitsim.simulator.Circuit; import com.ra4king.circuitsim.simulator.CircuitState; import com.ra4king.circuitsim.simulator.Component; -import com.ra4king.circuitsim.simulator.components.memory.Register; import com.ra4king.circuitsim.simulator.components.wiring.Pin; import com.ra4king.circuitsim.simulator.Port; import com.ra4king.circuitsim.simulator.Simulator; @@ -46,34 +44,20 @@ public class Subcircuit { private String name; private CircuitSim circuitSim; - private CircuitManager circuitManager; - + private SubcircuitState state; // Used for lookups of components by name - private Set categoryNamesKnown = new HashSet<>(); - private Set componentNamesKnown = new HashSet<>(); - private Map>, - Pair> componentClassNames; + private ComponentNameInfo componentNameInfo; - private Subcircuit(String name, CircuitSim circuitSim, CircuitManager circuitManager) { + private Subcircuit( + String name, CircuitSim circuitSim, SubcircuitState state, ComponentNameInfo componentNameInfo) { this.name = name; this.circuitSim = circuitSim; - this.circuitManager = circuitManager; - // The category/component names are buried in some semi-internal - // CircuitSim data structures, so pull them out and make more - // efficient ways to access them - initComponentNames(); - } - - // Find the names of CircuitSim components and component categories - private void initComponentNames() { - categoryNamesKnown = new HashSet<>(); - componentNamesKnown = new HashSet<>(); - componentClassNames = new HashMap<>(); - circuitSim.getComponentManager().forEach((ComponentManager.ComponentLauncherInfo info) -> { - categoryNamesKnown.add(info.name.getKey()); - componentNamesKnown.add(info.name.getValue()); - componentClassNames.put(info.clazz, info.name); - }); + this.state = state; + this.componentNameInfo = componentNameInfo; + } + + private Subcircuit(String name, CircuitSim circuitSim, SubcircuitState state) { + this(name, circuitSim, state, ComponentNameInfo.fromCircuitSim(circuitSim)); } /** @@ -81,7 +65,7 @@ private void initComponentNames() { * * @return a {@code String} holding the subcircuit name. Not * normalized, so represents exactly what the test file specified. - * @see fromPath for information on subcircuit name normalization + * @see #fromPath(String, String) for information on subcircuit name normalization */ public String getName() { return name; @@ -121,7 +105,11 @@ public Simulator getSimulator() { * @return the {@code CircuitBoard} of this subcircuit */ public CircuitBoard getCircuitBoard() { - return circuitManager.getCircuitBoard(); + return getCircuitManager().getCircuitBoard(); + } + + public CircuitManager getCircuitManager() { + return state.circuitManager; } /** @@ -146,7 +134,7 @@ public Circuit getCircuit() { * @return the {@code CircuitState} of top level state of this subcircuit */ public CircuitState getCircuitState() { - return getCircuit().getTopLevelState(); + return state.circuitState; } // Don't hate me for `throws Exception', Roi made me do it @@ -184,15 +172,23 @@ public static Subcircuit fromPath(String simFilePath, String subcircuitName) thr CircuitSim circuitSim = new CircuitSim(false); circuitSim.loadCircuits(circuitFile); - CircuitManager circuitManager = lookupSubcircuit(circuitSim, subcircuitName); - return new Subcircuit(subcircuitName, circuitSim, circuitManager); + return new Subcircuit(subcircuitName, circuitSim, lookupSubcircuit(circuitSim, subcircuitName)); + } + + // Create another version of this instance, except with a different + // CircuitManager + private Subcircuit withSubcircuitState(SubcircuitState state) { + // Don't create an unnecessary new instance + return Objects.equals(this.state, state) + ? this + : new Subcircuit(name, circuitSim, state, componentNameInfo); } private static String canonicalName(String name) { return name.toLowerCase().replaceAll("[^0-9a-z]+", ""); } - private static CircuitManager lookupSubcircuit(CircuitSim circuitSim, String subcircuitName) { + private static SubcircuitState lookupSubcircuit(CircuitSim circuitSim, String subcircuitName) { String canonicalSubcircuitName = canonicalName(subcircuitName); Map managers = circuitSim.getCircuitManagers(); @@ -213,7 +209,8 @@ private static CircuitManager lookupSubcircuit(CircuitSim circuitSim, String sub subcircuitName)); } - return matchingCircuits.get(0); + CircuitManager manager = matchingCircuits.get(0); + return new SubcircuitState(manager, manager.getCircuit().getTopLevelState()); } /** @@ -257,20 +254,22 @@ public Map lookupComponentCounts( boolean recursive) { return lookupComponentPeers(componentNames, inverse, recursive) .entrySet().stream().collect(Collectors.toMap( - entry -> entry.getKey(), entry -> entry.getValue().size())); + Map.Entry::getKey, entry -> entry.getValue().size())); } + // TODO: delete me???? private Map> lookupComponents( Collection componentNames, boolean inverse, boolean recursive) { return lookupComponentPeers(componentNames, inverse, recursive) .entrySet().stream().collect(Collectors.toMap( - entry -> entry.getKey(), - entry -> entry.getValue().stream().map(peer -> peer.getComponent()) + Map.Entry::getKey, + entry -> entry.getValue().stream().map(ComponentPeer::getComponent) .collect(Collectors.toSet()))); } + private Map>> lookupComponentPeers( Collection componentNames, boolean inverse, @@ -279,9 +278,9 @@ private Map>> lookupComponentPeers( // set of category names Set components = new HashSet<>(componentNames); Set goalCategories = new HashSet<>(components); - goalCategories.retainAll(categoryNamesKnown); + goalCategories.retainAll(componentNameInfo.getCategoryNamesKnown()); Set goalComponents = new HashSet<>(components); - goalComponents.retainAll(componentNamesKnown); + goalComponents.retainAll(componentNameInfo.getComponentNamesKnown()); // Look for bogus entries in the list passed by the programmer components.removeAll(goalCategories); @@ -292,13 +291,26 @@ private Map>> lookupComponentPeers( String.join(", ", components))); } + Map>> matchingComponents = new HashMap<>(); // Run a depth-first search through the simulation DAG starting - // at this subcircuit - ComponentDFS dfs = new ComponentDFS( - goalCategories, goalComponents, inverse, recursive); - return dfs.run(getCircuitBoard()); + // at this subcircuit. (Setting revisit=false makes this a DFS) + walk(recursive, false, (circuitBoard, component) -> { + Pair name = componentNameInfo.getPeerCategoryAndName(component.getClass()); + boolean match = goalCategories.contains(name.getKey()) || + goalComponents.contains(name.getValue()); + + if (match ^ inverse) { + matchingComponents.computeIfAbsent( + name.getValue(), + k -> new HashSet<>()).add(component); + } + }); + + return matchingComponents; } + // TODO FIXME update docs + // pinLabel == null means only pin /** * Finds a Pin component labelled {@code pinLabel} in this * subcircuit and returns a wrapper around it. @@ -322,52 +334,159 @@ private Map>> lookupComponentPeers( * @see InputPin * @see OutputPin */ - public BasePin lookupPin(String pinLabel, boolean wantInputPin, int wantBits) { - String canonicalPinLabel = canonicalName(pinLabel); - List matchingPins = getCircuitBoard().getComponents().stream() - .filter(component -> component instanceof PinPeer && - ((PinPeer) component).getProperties().containsProperty(Properties.LABEL) && - canonicalPinLabel.equals( - canonicalName(((PinPeer) component).getProperties().getValue(Properties.LABEL)))) - .map(component -> (PinPeer) component) - .collect(Collectors.toList()); - - if (matchingPins.size() > 1) { - throw new IllegalArgumentException(String.format( - "Subcircuit `%s' contains %d input/output pins labelled `%s', expected 1", - getCircuitBoard().getCircuit().getName(), matchingPins.size(), canonicalPinLabel)); - } else if (matchingPins.isEmpty()) { - throw new IllegalArgumentException(String.format( - "Subcircuit `%s' contains no input/output pins labelled `%s'!", - getCircuitBoard().getCircuit().getName(), canonicalPinLabel)); + public BasePin lookupPin(String pinLabel, boolean wantInputPin, + int wantBits, boolean recursive) { + // TODO: InputPin.get() does not exist, but if it does later, this + // should actually be supported + if (recursive && wantInputPin) { + throw new IllegalArgumentException( + "Can't recursively locate an input pin; consider how " + + "changing its values would affect parent circuits"); } - PinPeer matchingPin = matchingPins.get(0); + Pair match = lookupComponent( + pinLabel, wantBits, recursive, PinPeer.class); + SubcircuitState matchingState = match.getKey(); + PinPeer matchingPin = match.getValue(); + // Use their labels for error messages to be less confusing String theirPinLabel = matchingPin.getProperties().getValue(Properties.LABEL); if (matchingPin.isInput() != wantInputPin) { throw new IllegalArgumentException(String.format( "Subcircuit `%s' has %s pin labelled `%s', but expected it to be an %s pin instead", - getCircuitBoard().getCircuit().getName(), - theirPinLabel, + matchingState.circuitManager.getCircuitBoard().getCircuit().getName(), matchingPin.isInput()? "input" : "output", + theirPinLabel, wantInputPin? "input" : "output")); } - int actualBits = matchingPin.getComponent().getBitSize(); - if (actualBits != wantBits) { + Subcircuit subcircuit = withSubcircuitState(matchingState); + BasePin pinWrapper = wantInputPin? new InputPin(matchingPin.getComponent(), + subcircuit) + : new OutputPin(matchingPin.getComponent(), + subcircuit); + return pinWrapper; + } + + // FIXME TODO document me + // regLabel == null means only register + public Register lookupRegister(String regLabel, int wantBits, boolean recursive) { + Pair match = + lookupComponent(regLabel, wantBits, recursive, RegisterPeer.class); + SubcircuitState matchingState = match.getKey(); + com.ra4king.circuitsim.simulator.components.memory.Register matchingRegister = + match.getValue().getComponent(); + + Subcircuit subcircuit = withSubcircuitState(matchingState); + return new Register(matchingRegister, subcircuit); + } + + public enum MemoryType { + ROM, + RAM + } + + public BaseMemory lookupMemory(String label, int wantBits, boolean recursive, MemoryType type) { + switch (type) { + case ROM: + Pair romMatch = + lookupComponent(label, wantBits, recursive, ROMPeer.class); + return new Rom(romMatch.getValue().getComponent(), withSubcircuitState(romMatch.getKey())); + case RAM: + Pair ramMatch = + lookupComponent(label, wantBits, recursive, RAMPeer.class); + return new Ram(ramMatch.getValue().getComponent(), withSubcircuitState(ramMatch.getKey())); + default: + throw new IllegalArgumentException("unknown memory type " + type.name()); + } + } + + // TODO: support recursive + public OutputPin snitchTunnel(String label, int wantBits) { + String canonicalLabel = canonicalName(label); + // Hack used because variables referenced in Lambdas must be + // effectively final: https://stackoverflow.com/a/32523185/321301 + Tunnel[] tunnelFound = {null}; + + walk(false, false, (state, peer) -> { + if (peer instanceof Tunnel && + canonicalName(peer.getProperties().getValue(Properties.LABEL)).equals(canonicalName(label))) { + int actualBits = peer.getProperties().getValue(Properties.BITSIZE); + if (actualBits != wantBits) { + throw new IllegalArgumentException(String.format( + "Tunnel `%s' in subcircuit `%s' should have %d bits, not %d", + label, state.circuitManager.getCircuitBoard().getCircuit().getName(), + wantBits, actualBits)); + } + tunnelFound[0] = (Tunnel) peer; + } + }); + + Tunnel tunnel = tunnelFound[0]; + if (tunnel == null) { throw new IllegalArgumentException(String.format( - "Subcircuit `%s' has pin labelled `%s' with %d bits, but expected %d bits", + "No tunnel `%s' found in subcircuit `%s'", label, getCircuit().getName())); + } + + Port.Link tunnelLink = tunnel.getComponent().getPort(0).getLink(); + Pin snitchPin = addSnitchPinToLink(tunnelLink, false); + return new OutputPin(snitchPin, this); + } + + // label == null means look for the only component + private > Pair lookupComponent( + String label, int wantBits, boolean recursive, Class peerClass) { + + String componentName = componentNameInfo.getPeerCategoryAndName(peerClass).getValue(); + String canonicalLabel = (label == null)? null : canonicalName(label); + + List> matches = new LinkedList<>(); + // If we're doing recursive, we need to revisit to make sure we + // find duplicates + walk(recursive, true, (state, componentPeer) -> { + if (peerClass.isInstance(componentPeer) && + (label == null || canonicalLabel.equals(canonicalName(componentPeer.getProperties() + .getValue(Properties.LABEL))))) { + matches.add(new Pair<>(state, peerClass.cast(componentPeer))); + } + }); + + String searchCriteria = (label == null)? "" + : String.format(" labelled `%s'", label); + + if (matches.size() > 1) { + throw new IllegalArgumentException(String.format( + "Subcircuit `%s'%s contains %d %ss%s, expected 1", + getCircuitBoard().getCircuit().getName(), + recursive? " (and its children)" : "", + matches.size(), componentName, searchCriteria)); + } else if (matches.isEmpty()) { + throw new IllegalArgumentException(String.format( + "Subcircuit `%s'%s contains no %ss%s!", getCircuitBoard().getCircuit().getName(), - theirPinLabel, actualBits, wantBits)); + recursive? " (and its children)" : "", + componentName, searchCriteria)); } - BasePin pinWrapper = wantInputPin? new InputPin(matchingPin.getComponent(), this) - : new OutputPin(matchingPin.getComponent(), this); - return pinWrapper; + Pair matchingPair = matches.get(0); + if (wantBits > 0) { + SubcircuitState matchingState = matchingPair.getKey(); + T matchingPeer = matchingPair.getValue(); + + int actualBits = matchingPeer.getProperties().getValue(Properties.BITSIZE); + if (actualBits != wantBits) { + throw new IllegalArgumentException(String.format( + "Subcircuit `%s' has %s%s with %d bits, but expected %d bits", + matchingState.circuitManager.getCircuitBoard().getCircuit().getName(), + componentName, searchCriteria, actualBits, wantBits)); + } + } + + return matchingPair; } + // TODO FIXME update docs /** * Mocks the only register in this subcircuit by replacing it with * input and output pins. Useful for testing the combinational logic @@ -390,38 +509,72 @@ public BasePin lookupPin(String pinLabel, boolean wantInputPin, int wantBits) { * collection of input and output pins * @see MockRegister */ - public MockRegister mockOnlyRegister(int wantBits) { - List registers = getCircuitBoard().getComponents().stream() - .filter(component -> component instanceof RegisterPeer) - .map(component -> ((RegisterPeer) component).getComponent()) - .collect(Collectors.toList()); + MockRegister mockRegister(com.ra4king.circuitsim.simulator.components.memory.Register reg) { + //List registers = getCircuitBoard().getComponents().stream() + // .filter(component -> component instanceof RegisterPeer) + // .map(component -> ((RegisterPeer) component).getComponent()) + // .collect(Collectors.toList()); + + //if (registers.size() > 1) { + // throw new IllegalArgumentException(String.format( + // "Subcircuit `%s' contains %d registers, expected 1", + // getCircuitBoard().getCircuit().getName(), registers.size())); + //} else if (registers.isEmpty()) { + // throw new IllegalArgumentException(String.format( + // "Subcircuit `%s' contains no registers!", + // getCircuitBoard().getCircuit().getName())); + //} + + //Register reg = registers.get(0); + + //int actualBits = reg.getBitSize(); + //if (actualBits != wantBits) { + // throw new IllegalArgumentException(String.format( + // "Subcircuit `%s' has register with %d bits, but expected %d bits", + // getCircuitBoard().getCircuit().getName(), actualBits, wantBits)); + //} + + //com.ra4king.circuitsim.simulator.components.memory.Register reg = regWrapper.getRegister(); + + InputPin q = new InputPin(substitutePin(reg.getPort(PORT_OUT), true), this); + OutputPin d = new OutputPin(substitutePin(reg.getPort(PORT_IN), false), this); + OutputPin en = new OutputPin(substitutePin(reg.getPort(PORT_ENABLE), false), this); + OutputPin clk = new OutputPin(substitutePin(reg.getPort(PORT_CLK), false), this); + OutputPin rst = new OutputPin(substitutePin(reg.getPort(PORT_ZERO), false), this); - if (registers.size() > 1) { - throw new IllegalArgumentException(String.format( - "Subcircuit `%s' contains %d registers, expected 1", - getCircuitBoard().getCircuit().getName(), registers.size())); - } else if (registers.isEmpty()) { - throw new IllegalArgumentException(String.format( - "Subcircuit `%s' contains no registers!", - getCircuitBoard().getCircuit().getName())); - } + return new MockRegister(q, d, en, clk, rst, this); + } - Register reg = registers.get(0); + public enum PulserType { + CLOCK, + BUTTON + } - int actualBits = reg.getBitSize(); - if (actualBits != wantBits) { - throw new IllegalArgumentException(String.format( - "Subcircuit `%s' has register with %d bits, but expected %d bits", - getCircuitBoard().getCircuit().getName(), actualBits, wantBits)); + public MockPulser mockPulser(String label, int wantBits, boolean recursive, PulserType type) { + SubcircuitState matchingState; + ComponentPeer matchingComponent; + + switch (type) { + case CLOCK: + Pair clockMatch = lookupComponent(label, wantBits, recursive, ClockPeer.class); + matchingState = clockMatch.getKey(); + matchingComponent = clockMatch.getValue(); + break; + case BUTTON: + Pair buttonMatch = + lookupComponent(label, wantBits, recursive, com.ra4king.circuitsim.gui.peers.io.Button.class); + matchingState = buttonMatch.getKey(); + matchingComponent = buttonMatch.getValue(); + break; + default: + throw new IllegalArgumentException("unknown pulser type " + type.name()); } - InputPin q = new InputPin(substitutePin(reg.getPort(Register.PORT_OUT), true), this); - OutputPin d = new OutputPin(substitutePin(reg.getPort(Register.PORT_IN), false), this); - OutputPin en = new OutputPin(substitutePin(reg.getPort(Register.PORT_ENABLE), false), this); - OutputPin clk = new OutputPin(substitutePin(reg.getPort(Register.PORT_CLK), false), this); - OutputPin rst = new OutputPin(substitutePin(reg.getPort(Register.PORT_ZERO), false), this); - - return new MockRegister(q, d, en, clk, rst, this); + Subcircuit subcircuit = withSubcircuitState(matchingState); + Port onlyPort = matchingComponent.getComponent().getPort(0); + InputPin mockPin = new InputPin(subcircuit.substitutePin(onlyPort, true), subcircuit); + return (type == PulserType.BUTTON)? new Button(mockPin, subcircuit) + : new Clock(mockPin, subcircuit); } private Port.Link makeOrphanPort(Port port) { @@ -431,18 +584,20 @@ private Port.Link makeOrphanPort(Port port) { } private int getMaxX() { - return (int) Math.ceil(circuitManager.getCanvas().getWidth() / GuiUtils.BLOCK_SIZE); + return (int) Math.ceil(state.circuitManager.getCanvas().getWidth() / GuiUtils.BLOCK_SIZE); } private int getMaxY() { - return (int) Math.ceil(circuitManager.getCanvas().getHeight() / GuiUtils.BLOCK_SIZE); + return (int) Math.ceil(state.circuitManager.getCanvas().getHeight() / GuiUtils.BLOCK_SIZE); } private Pin substitutePin(Port port, boolean isInput) { - Port.Link originalLink = makeOrphanPort(port); + return addSnitchPinToLink(makeOrphanPort(port), isInput); + } + private Pin addSnitchPinToLink(Port.Link link, boolean isInput) { PinPeer mockPinPeer = new PinPeer(new Properties( - new Properties.Property<>(Properties.BITSIZE, port.getLink().getBitSize()), + new Properties.Property<>(Properties.BITSIZE, link.getBitSize()), new Properties.Property<>(PinPeer.IS_INPUT, isInput) ), getMaxX(), getMaxY()); @@ -457,72 +612,110 @@ private Pin substitutePin(Port port, boolean isInput) { Pin mockPin = mockPinPeer.getComponent(); Port newPort = mockPin.getPort(Pin.PORT); // Now reconnect all the old stuff - originalLink.linkPort(newPort); + link.linkPort(newPort); return mockPin; } - private class ComponentDFS { - private Set goalCategories; - private Set goalComponents; - private boolean inverse; - private boolean recursive; - private Set visitedSubcircuits; - private Map>> matchingComponents; - - public ComponentDFS ( - Set goalCategories, - Set goalComponents, - boolean inverse, - boolean recursive) { - this.goalCategories = goalCategories; - this.goalComponents = goalComponents; - this.inverse = inverse; - this.recursive = recursive; + private void walk(boolean recursive, boolean revisit, BiConsumer> consumer) { + Set visitedSubcircuits = revisit? Collections.emptySet() + : new HashSet<>(); + walk(visitedSubcircuits, state, recursive, revisit, consumer); + } + + private void walk(Set visitedSubcircuits, + SubcircuitState state, + boolean recursive, + boolean revisit, + BiConsumer> consumer) { + for (ComponentPeer component : state.circuitManager.getCircuitBoard().getComponents()) { + boolean isSubcircuit = component instanceof SubcircuitPeer; + + if (isSubcircuit && recursive) { + SubcircuitPeer subcircuitPeer = (SubcircuitPeer) component; + String subcircuitName = subcircuitPeer.getProperties() + .getProperty(SubcircuitPeer.SUBCIRCUIT) + .getStringValue(); + if (!visitedSubcircuits.contains(subcircuitName)) { + if (!revisit) + visitedSubcircuits.add(subcircuitName); + + CircuitManager childCircuitManager = subcircuitPeer.getProperties() + .getValue(SubcircuitPeer.SUBCIRCUIT); + CircuitState childCircuitState = subcircuitPeer.getComponent() + .getSubcircuitState(state.circuitState); + SubcircuitState childState = new SubcircuitState(childCircuitManager, childCircuitState); + walk(visitedSubcircuits, childState, recursive, revisit, consumer); + } + } else if (!isSubcircuit) { + consumer.accept(state, component); + } } + } - public Map>> run(CircuitBoard circuitBoard) { - visitedSubcircuits = new HashSet<>(); - matchingComponents = new HashMap<>(); - lookupComponentsDfs(circuitBoard); - return matchingComponents; + private static class SubcircuitState { + private CircuitManager circuitManager; + private CircuitState circuitState; + + private SubcircuitState(CircuitManager circuitManager, CircuitState circuitState) { + this.circuitManager = circuitManager; + this.circuitState = circuitState; } - private void lookupComponentsDfs(CircuitBoard circuitBoard) { - for (ComponentPeer component : circuitBoard.getComponents()) { - boolean isSubcircuit = component instanceof SubcircuitPeer; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SubcircuitState that = (SubcircuitState) o; + return Objects.equals(circuitManager, that.circuitManager) && + Objects.equals(circuitState, that.circuitState); + } + } - if (isSubcircuit && recursive) { - SubcircuitPeer subcircuitPeer = (SubcircuitPeer) component; - String subcircuitName = subcircuitPeer.getProperties() - .getProperty(SubcircuitPeer.SUBCIRCUIT) - .getStringValue(); - if (!visitedSubcircuits.contains(subcircuitName)) { - visitedSubcircuits.add(subcircuitName); + private static class ComponentNameInfo { + private Set categoryNamesKnown; + private Set componentNamesKnown; + private Map>, + Pair> componentClassNames; - CircuitManager childCircuitManager = (CircuitManager)( - subcircuitPeer.getProperties() - .getValue(SubcircuitPeer.SUBCIRCUIT)); - CircuitBoard childCircuitBoard = childCircuitManager.getCircuitBoard(); - lookupComponentsDfs(childCircuitBoard); - } - } else if (!isSubcircuit) { - Pair name = componentClassNames.get(component.getClass()); - - if (name == null) { - throw new IllegalStateException(String.format( - "Unknown component %s", component.getClass())); - } - - boolean match = goalCategories.contains(name.getKey()) || - goalComponents.contains(name.getValue()); - if (match ^ inverse) { - matchingComponents.computeIfAbsent( - name.getValue(), - k -> new HashSet<>()).add(component); - } - } + private ComponentNameInfo(Set categoryNamesKnown, + Set componentNamesKnown, + Map>, Pair> componentClassNames) { + this.categoryNamesKnown = categoryNamesKnown; + this.componentNamesKnown = componentNamesKnown; + this.componentClassNames = componentClassNames; + } + + // Find the names of CircuitSim components and component categories + public static ComponentNameInfo fromCircuitSim(CircuitSim circuitSim) { + Set categoryNamesKnown = new HashSet<>(); + Set componentNamesKnown = new HashSet<>(); + Map>, + Pair> componentClassNames = new HashMap<>(); + + circuitSim.getComponentManager().forEach((ComponentManager.ComponentLauncherInfo info) -> { + categoryNamesKnown.add(info.name.getKey()); + componentNamesKnown.add(info.name.getValue()); + componentClassNames.put(info.clazz, info.name); + }); + + return new ComponentNameInfo(categoryNamesKnown, componentNamesKnown, componentClassNames); + } + + public Pair getPeerCategoryAndName(Class peerClass) { + Pair name = componentClassNames.get(peerClass); + if (name == null) { + throw new IllegalStateException(String.format("Unknown component %s", peerClass)); } + return name; + } + + public Set getCategoryNamesKnown() { + return Collections.unmodifiableSet(categoryNamesKnown); + } + + public Set getComponentNamesKnown() { + return Collections.unmodifiableSet(componentNamesKnown); } } } diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/api/SubcircuitPin.java b/src/main/java/edu/gatech/cs2110/circuitsim/api/SubcircuitComponent.java similarity index 63% rename from src/main/java/edu/gatech/cs2110/circuitsim/api/SubcircuitPin.java rename to src/main/java/edu/gatech/cs2110/circuitsim/api/SubcircuitComponent.java index 44b5a43..9a2e3c3 100644 --- a/src/main/java/edu/gatech/cs2110/circuitsim/api/SubcircuitPin.java +++ b/src/main/java/edu/gatech/cs2110/circuitsim/api/SubcircuitComponent.java @@ -22,7 +22,7 @@ * @see OutputPin */ @Retention(RUNTIME) -public @interface SubcircuitPin { +public @interface SubcircuitComponent { /** * The label of a matching Pin. If empty (the default), {@link * edu.gatech.cs2110.circuitsim.extension.CircuitSimExtension} will @@ -37,5 +37,28 @@ * * @return The bit size of a matching Pin. */ - int bits(); + // TODO FIXME document when this is requried + int bits() default -1; + + boolean onlyInstance() default false; + + // TODO FIXME document + boolean recursiveSearch() default false; + + /** + * The type of "pin". The default is a literal existing Pin + * component, but you can set this to {@code TUNNEL} to spy on the + * value of a tunnel by attaching an OutputPin to it. (The label is + * then the label of the tunnel you want to spy instead of the label + * of an existing pin.) + * + * @return An enum value determining if this should spy on a tunnel + * instead of controlling a Pin component + */ + Type type() default Type.INFER; + + enum Type { + INFER, + TUNNEL, + } } diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/api/SubcircuitRegister.java b/src/main/java/edu/gatech/cs2110/circuitsim/api/SubcircuitRegister.java deleted file mode 100644 index 85e178d..0000000 --- a/src/main/java/edu/gatech/cs2110/circuitsim/api/SubcircuitRegister.java +++ /dev/null @@ -1,37 +0,0 @@ -package edu.gatech.cs2110.circuitsim.api; - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import java.lang.annotation.Retention; - -/** - * Instructs {@link edu.gatech.cs2110.circuitsim.extension.CircuitSimExtension} - * to find and inject a {@link MockRegister} for a Register component - * into a {@link MockRegister} field in a test class. - *

- * When you annotate a {@link MockRegister} field in a test class with - * this, {@link edu.gatech.cs2110.circuitsim.extension.CircuitSimExtension} - * will use {@link Subcircuit#mockOnlyRegister(int)} to replace the only - * register in the file with a "ghost register." See {@link - * MockRegister} for details. - * - * @see MockRegister - * @see Subcircuit#mockOnlyRegister(int) - */ -@Retention(RUNTIME) -public @interface SubcircuitRegister { - /** - * Whether to match the only register in the subcircuit. - * - * @return true if you want the only register in the file, false - * otherwise (default but currently not supported). - */ - boolean onlyRegister() default false; - - /** - * Desired bitsize of a matching register. - * - * @return bitsize of a matching register - */ - int bits(); -} diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/extension/CircuitSimExtension.java b/src/main/java/edu/gatech/cs2110/circuitsim/extension/CircuitSimExtension.java index 875c04e..caa17ce 100644 --- a/src/main/java/edu/gatech/cs2110/circuitsim/extension/CircuitSimExtension.java +++ b/src/main/java/edu/gatech/cs2110/circuitsim/extension/CircuitSimExtension.java @@ -2,26 +2,21 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; -import java.util.Arrays; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; +import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; +import edu.gatech.cs2110.circuitsim.api.*; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.Extension; -import edu.gatech.cs2110.circuitsim.api.BasePin; -import edu.gatech.cs2110.circuitsim.api.InputPin; -import edu.gatech.cs2110.circuitsim.api.MockRegister; -import edu.gatech.cs2110.circuitsim.api.OutputPin; -import edu.gatech.cs2110.circuitsim.api.Restrictor; -import edu.gatech.cs2110.circuitsim.api.Subcircuit; -import edu.gatech.cs2110.circuitsim.api.SubcircuitPin; -import edu.gatech.cs2110.circuitsim.api.SubcircuitRegister; -import edu.gatech.cs2110.circuitsim.api.SubcircuitTest; +import static edu.gatech.cs2110.circuitsim.api.Subcircuit.MemoryType.RAM; +import static edu.gatech.cs2110.circuitsim.api.Subcircuit.MemoryType.ROM; +import static edu.gatech.cs2110.circuitsim.api.Subcircuit.PulserType.BUTTON; +import static edu.gatech.cs2110.circuitsim.api.Subcircuit.PulserType.CLOCK; +import static edu.gatech.cs2110.circuitsim.api.SubcircuitComponent.Type.TUNNEL; /** * Extends JUnit to understand testing CircuitSim subcircuits. @@ -53,9 +48,7 @@ public void beforeAll(ExtensionContext context) throws Exception { runRestrictor(subcircuit, restrictor); } - fieldInjections = new LinkedList<>(); - fieldInjections.addAll(generatePinFieldInjections(testClass)); - fieldInjections.addAll(generateRegFieldInjections(testClass)); + fieldInjections = generateFieldInjections(testClass); } @Override @@ -97,67 +90,117 @@ private void runRestrictor( } } - private Collection generatePinFieldInjections(Class testClass) { - List fieldInjections = new LinkedList<>(); - - List pinFields = Arrays.stream(testClass.getDeclaredFields()) - .filter(field -> field.isAnnotationPresent(SubcircuitPin.class)) - .collect(Collectors.toList()); + private List generateFieldInjections(Class testClass) { + Map, Function> fieldInjectors = new HashMap<>(); + fieldInjectors.put(InputPin.class, this::generatePinFieldInjection); + fieldInjectors.put(OutputPin.class, this::generatePinFieldInjection); + fieldInjectors.put(Ram.class, this::generateMemoryFieldInjection); + fieldInjectors.put(Rom.class, this::generateMemoryFieldInjection); + fieldInjectors.put(Register.class, this::generateRegFieldInjection); + fieldInjectors.put(MockRegister.class, this::generateRegFieldInjection); + fieldInjectors.put(Clock.class, this::generatePulserFieldInjection); + fieldInjectors.put(Button.class, this::generatePulserFieldInjection); // TODO: Detect duplicate pins (`Pin a' and `Pin A' are distinct // fields in Java but not here). Easy solution: sort the // stream by canonicalName(field.getName()) and iterate // over the resulting List, comparing each with the // next + return Arrays.stream(testClass.getDeclaredFields()) + .filter(field -> field.isAnnotationPresent(SubcircuitComponent.class)) + .map(field -> Optional.ofNullable(fieldInjectors.get(field.getType())) + .orElseThrow(() -> new IllegalArgumentException( + "Test class field " + field.getName() + " in " + + testClass.getCanonicalName() + " annotated with " + + "@SubcircuitComponent has unknown type " + field.getType())) + .apply(field)) + .collect(Collectors.toList()); + } + + private FieldInjection generatePinFieldInjection(Field field) { + SubcircuitComponent pinAnnotation = field.getDeclaredAnnotation(SubcircuitComponent.class); + + if (pinAnnotation.bits() <= 0) { + throw new IllegalArgumentException(field.getName() + + " is an Input/Output Pins, so @SubcircuitComponent needs a positive bits parameter"); + } - for (Field pinField : pinFields) { - if (!BasePin.class.isAssignableFrom(pinField.getType())) { + BasePin pinWrapper; + if (pinAnnotation.type() == TUNNEL) { + if (pinAnnotation.onlyInstance()) { throw new IllegalArgumentException( - "Test class fields annotated with @SubcircuitPin should be of type InputPin or OutputPin"); + field.getName() + " is a tunnel, please don't specify " + + "onlyInstance on @SubcircuitComponent, you're better than this"); + } + if (pinAnnotation.recursiveSearch()) { + throw new IllegalArgumentException( + field.getName() + " is a tunnel, recursiveSearch on " + + "@SubcircuitComponent is not implemented"); } - boolean wantInputPin = InputPin.class.isAssignableFrom(pinField.getType()); - SubcircuitPin pinAnnotation = pinField.getDeclaredAnnotation(SubcircuitPin.class); - String pinLabel = pinAnnotation.label().isEmpty()? pinField.getName() : pinAnnotation.label(); - - BasePin pinWrapper = subcircuit.lookupPin(pinLabel, wantInputPin, pinAnnotation.bits()); - fieldInjections.add(new FieldInjection(pinField, pinWrapper)); + String label = pinAnnotation.label().isEmpty()? field.getName() : pinAnnotation.label(); + pinWrapper = subcircuit.snitchTunnel(label, pinAnnotation.bits()); + } else { // INFER + boolean wantInputPin = InputPin.class.equals(field.getType()); + String pinLabel = pinAnnotation.onlyInstance()? null : + pinAnnotation.label().isEmpty()? field.getName() : pinAnnotation.label(); + pinWrapper = subcircuit.lookupPin(pinLabel, wantInputPin, pinAnnotation.bits(), pinAnnotation.recursiveSearch()); } - return fieldInjections; + return new FieldInjection(field, pinWrapper); } - private Collection generateRegFieldInjections(Class testClass) { - List fieldInjections = new LinkedList<>(); + private FieldInjection generateRegFieldInjection(Field field) { + SubcircuitComponent regAnnotation = field.getDeclaredAnnotation(SubcircuitComponent.class); - List regFields = Arrays.stream(testClass.getDeclaredFields()) - .filter(field -> field.isAnnotationPresent(SubcircuitRegister.class)) - .collect(Collectors.toList()); + if (regAnnotation.bits() <= 0) { + throw new IllegalArgumentException(field.getName() + + " is an Register, so @SubcircuitComponent needs a positive bits parameter"); + } - // TODO: Like in generatePinFieldInjections(), search for dupe - // fields in the class itself + String regLabel = regAnnotation.onlyInstance()? null : + regAnnotation.label().isEmpty()? field.getName() : regAnnotation.label(); + Register reg = subcircuit.lookupRegister(regLabel, regAnnotation.bits(), + regAnnotation.recursiveSearch()); + + boolean wantMockRegister = MockRegister.class.isAssignableFrom(field.getType()); + if (wantMockRegister) { + MockRegister mockRegister = reg.mock(); + return new FieldInjection(field, mockRegister); + } else { + return new FieldInjection(field, reg); + } + } - for (Field regField : regFields) { - if (!MockRegister.class.isAssignableFrom(regField.getType())) { - throw new IllegalArgumentException( - "Test class fields annotated with @SubcircuitRegister should be of type MockRegister"); - } + private FieldInjection generateMemoryFieldInjection(Field field) { + SubcircuitComponent componentAnnotation = field.getDeclaredAnnotation(SubcircuitComponent.class); - SubcircuitRegister regAnnotation = regField.getDeclaredAnnotation(SubcircuitRegister.class); + if (componentAnnotation.bits() <= 0) { + throw new IllegalArgumentException(field.getName() + + " is RAM/ROM, so @SubcircuitComponent needs a positive bits parameter"); + } - // I added this field to the annotation so that we don't - // break existing tests down the road if we implement this - if (!regAnnotation.onlyRegister()) { - throw new UnsupportedOperationException( - "@SubcircuitRegister(onlyRegister=false, ...) is not yet supported. " + - "(Note: the default is onlyRegister=false, so you'll need to write onlyRegister=true.)"); - } + String label = componentAnnotation.onlyInstance()? null : + componentAnnotation.label().isEmpty()? field.getName() : componentAnnotation.label(); + Subcircuit.MemoryType type = field.getType().equals(Ram.class)? RAM : ROM; + BaseMemory wrapper = subcircuit.lookupMemory(label, componentAnnotation.bits(), + componentAnnotation.recursiveSearch(), type); + return new FieldInjection(field, wrapper); + } + + private FieldInjection generatePulserFieldInjection(Field field) { + SubcircuitComponent componentAnnotation = field.getDeclaredAnnotation(SubcircuitComponent.class); - MockRegister reg = subcircuit.mockOnlyRegister(regAnnotation.bits()); - fieldInjections.add(new FieldInjection(regField, reg)); + if (componentAnnotation.bits() >= 0) { + throw new IllegalArgumentException(field.getName() + + " is a Clock/Button, so @SubcircuitComponent does not need a bits parameter"); } - return fieldInjections; + String label = componentAnnotation.onlyInstance()? null : + componentAnnotation.label().isEmpty()? field.getName() : componentAnnotation.label(); + Subcircuit.PulserType type = field.getType().equals(Button.class)? BUTTON : CLOCK; + MockPulser mock = subcircuit.mockPulser(label, componentAnnotation.bits(), componentAnnotation.recursiveSearch(), type); + return new FieldInjection(field, mock); } private static class FieldInjection { diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/tests/AdderTests.java b/src/main/java/edu/gatech/cs2110/circuitsim/tests/AdderTests.java index 6d90951..e526eda 100644 --- a/src/main/java/edu/gatech/cs2110/circuitsim/tests/AdderTests.java +++ b/src/main/java/edu/gatech/cs2110/circuitsim/tests/AdderTests.java @@ -9,7 +9,7 @@ import edu.gatech.cs2110.circuitsim.api.InputPin; import edu.gatech.cs2110.circuitsim.api.OutputPin; -import edu.gatech.cs2110.circuitsim.api.SubcircuitPin; +import edu.gatech.cs2110.circuitsim.api.SubcircuitComponent; import edu.gatech.cs2110.circuitsim.api.SubcircuitTest; import edu.gatech.cs2110.circuitsim.extension.CircuitSimExtension; @@ -17,19 +17,19 @@ @ExtendWith(CircuitSimExtension.class) @SubcircuitTest(file="adder.sim", subcircuit="1-bit adder") public class AdderTests { - @SubcircuitPin(bits = 1) + @SubcircuitComponent(bits = 1) private InputPin a; - @SubcircuitPin(bits = 1) + @SubcircuitComponent(bits = 1) private InputPin b; - @SubcircuitPin(bits = 1) + @SubcircuitComponent(bits = 1) private InputPin cin; - @SubcircuitPin(bits = 1) + @SubcircuitComponent(bits = 1) private OutputPin sum; - @SubcircuitPin(bits = 1) + @SubcircuitComponent(bits = 1) private OutputPin cout; @ParameterizedTest(name="a:{0}, b:{1}, cin:{2} → cout:{3} + sum:{4}") diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/tests/FsmTests.java b/src/main/java/edu/gatech/cs2110/circuitsim/tests/FsmTests.java index 0f4cfac..3500f51 100644 --- a/src/main/java/edu/gatech/cs2110/circuitsim/tests/FsmTests.java +++ b/src/main/java/edu/gatech/cs2110/circuitsim/tests/FsmTests.java @@ -2,25 +2,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.LinkedList; -import java.util.List; -import java.util.stream.Stream; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.CsvFileSource; import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.MethodSource; import edu.gatech.cs2110.circuitsim.api.InputPin; import edu.gatech.cs2110.circuitsim.api.MockRegister; import edu.gatech.cs2110.circuitsim.api.OutputPin; -import edu.gatech.cs2110.circuitsim.api.SubcircuitPin; -import edu.gatech.cs2110.circuitsim.api.SubcircuitRegister; +import edu.gatech.cs2110.circuitsim.api.SubcircuitComponent; import edu.gatech.cs2110.circuitsim.api.SubcircuitTest; import edu.gatech.cs2110.circuitsim.extension.BasesConverter; import edu.gatech.cs2110.circuitsim.extension.CircuitSimExtension; @@ -29,22 +21,22 @@ @ExtendWith(CircuitSimExtension.class) @SubcircuitTest(file="fsm.sim", subcircuit="fsm") public class FsmTests { - @SubcircuitPin(bits=1) + @SubcircuitComponent(bits=1) private InputPin g; - @SubcircuitPin(bits=1) + @SubcircuitComponent(bits=1) private InputPin clk; - @SubcircuitPin(bits=1) + @SubcircuitComponent(bits=1) private InputPin rst; - @SubcircuitPin(bits=1) + @SubcircuitComponent(bits=1) private InputPin en; - @SubcircuitPin(bits=1) + @SubcircuitComponent(bits=1) private OutputPin a; - @SubcircuitRegister(bits=2, onlyRegister=true) + @SubcircuitComponent(bits=2, onlyInstance=true) private MockRegister stateReg; @DisplayName("Clock is connected") diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/tests/NOTTests.java b/src/main/java/edu/gatech/cs2110/circuitsim/tests/NOTTests.java index 6c43e9d..c530093 100644 --- a/src/main/java/edu/gatech/cs2110/circuitsim/tests/NOTTests.java +++ b/src/main/java/edu/gatech/cs2110/circuitsim/tests/NOTTests.java @@ -4,7 +4,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -12,7 +11,7 @@ import edu.gatech.cs2110.circuitsim.api.OutputPin; import edu.gatech.cs2110.circuitsim.api.Restrictor; import edu.gatech.cs2110.circuitsim.api.Subcircuit; -import edu.gatech.cs2110.circuitsim.api.SubcircuitPin; +import edu.gatech.cs2110.circuitsim.api.SubcircuitComponent; import edu.gatech.cs2110.circuitsim.api.SubcircuitTest; import edu.gatech.cs2110.circuitsim.extension.CircuitSimExtension; @@ -31,10 +30,10 @@ public void validate(Subcircuit subcircuit) throws AssertionError { } } - @SubcircuitPin(bits=1) + @SubcircuitComponent(bits=1) private InputPin in; - @SubcircuitPin(bits=1) + @SubcircuitComponent(bits=1) private OutputPin out; @ParameterizedTest(name="NOT in:{0} → out:{1}") diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/tests/ShiftRegisterTests.java b/src/main/java/edu/gatech/cs2110/circuitsim/tests/ShiftRegisterTests.java new file mode 100644 index 0000000..95cdd3c --- /dev/null +++ b/src/main/java/edu/gatech/cs2110/circuitsim/tests/ShiftRegisterTests.java @@ -0,0 +1,106 @@ +package edu.gatech.cs2110.circuitsim.tests; + +import edu.gatech.cs2110.circuitsim.api.*; +import edu.gatech.cs2110.circuitsim.extension.CircuitSimExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static edu.gatech.cs2110.circuitsim.api.SubcircuitComponent.Type.TUNNEL; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@DisplayName("Shift Register") +@ExtendWith(CircuitSimExtension.class) +@SubcircuitTest(file="shift-register.sim", subcircuit="main") +public class ShiftRegisterTests { + + @SubcircuitComponent(bits = 16) + private InputPin input; + + @SubcircuitComponent + private Clock clk; + + @SubcircuitComponent + private Button rst; + + @SubcircuitComponent(bits = 16) + private Register r0; + @SubcircuitComponent(bits = 16) + private Register r1; + @SubcircuitComponent(bits = 16, recursiveSearch = true) + private Register r2; + @SubcircuitComponent(bits = 16) + private Register r3; + + @SubcircuitComponent(bits = 16, type = TUNNEL) + private OutputPin end; + + @BeforeEach + public void reset() { + rst.press(); + } + + @DisplayName("stupid test") + @Test + public void testShiftRegister() { + input.set(0xf0f0); + assertEquals(0x0000, r0.getQ(), "r0 cycle 0"); + assertEquals(0x0000, r1.getQ(), "r1 cycle 0"); + assertEquals(0x0000, r2.getQ(), "r2 cycle 0"); + assertEquals(0x0000, r3.getQ(), "r3 cycle 0"); + assertEquals(0x0000, end.get(), "end cycle 0"); + + clk.tick(); + input.set(0x0f0f); + assertEquals(0xf0f0, r0.getQ(), "r0 cycle 1"); + assertEquals(0x0000, r1.getQ(), "r1 cycle 1"); + assertEquals(0x0000, r2.getQ(), "r2 cycle 1"); + assertEquals(0x0000, r3.getQ(), "r3 cycle 1"); + assertEquals(0x0000, end.get(), "end cycle 1"); + + clk.tick(); + input.set(0x0ff0); + assertEquals(0x0f0f, r0.getQ(), "r0 cycle 2"); + assertEquals(0xf0f0, r1.getQ(), "r1 cycle 2"); + assertEquals(0x0000, r2.getQ(), "r2 cycle 2"); + assertEquals(0x0000, r3.getQ(), "r3 cycle 2"); + assertEquals(0x0000, end.get(), "end cycle 2"); + + clk.tick(); + input.set(0xff00); + assertEquals(0x0ff0, r0.getQ(), "r0 cycle 3"); + assertEquals(0x0f0f, r1.getQ(), "r1 cycle 3"); + assertEquals(0xf0f0, r2.getQ(), "r2 cycle 3"); + assertEquals(0x0000, r3.getQ(), "r3 cycle 3"); + assertEquals(0x0000, end.get(), "end cycle 3"); + + clk.tick(); + input.set(0xbeef); + assertEquals(0xff00, r0.getQ(), "r0 cycle 4"); + assertEquals(0x0ff0, r1.getQ(), "r1 cycle 4"); + assertEquals(0x0f0f, r2.getQ(), "r2 cycle 4"); + assertEquals(0xf0f0, r3.getQ(), "r3 cycle 4"); + assertEquals(0xf0f0, end.get(), "end cycle 4"); + + clk.tick(); + assertEquals(0xbeef, r0.getQ(), "r0 cycle 5"); + assertEquals(0xff00, r1.getQ(), "r1 cycle 5"); + assertEquals(0x0ff0, r2.getQ(), "r2 cycle 5"); + assertEquals(0x0f0f, r3.getQ(), "r3 cycle 5"); + assertEquals(0x0f0f, end.get(), "end cycle 5"); + } + + @DisplayName("spicy test") + @Test + public void testShiftRegisterWithTickUntil() { + input.set(0xdead); + clk.tickUntil(16, () -> end.get() == 0xdead); + + assertEquals(0xdead, r0.getQ(), "r0"); + assertEquals(0xdead, r1.getQ(), "r1"); + assertEquals(0xdead, r2.getQ(), "r2"); + assertEquals(0xdead, r3.getQ(), "r3"); + assertEquals(0xdead, end.get(), "r3"); + } +} diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/tests/ToyALUTests.java b/src/main/java/edu/gatech/cs2110/circuitsim/tests/ToyALUTests.java index 743e803..2517a9b 100644 --- a/src/main/java/edu/gatech/cs2110/circuitsim/tests/ToyALUTests.java +++ b/src/main/java/edu/gatech/cs2110/circuitsim/tests/ToyALUTests.java @@ -23,7 +23,7 @@ import edu.gatech.cs2110.circuitsim.api.OutputPin; import edu.gatech.cs2110.circuitsim.api.Restrictor; import edu.gatech.cs2110.circuitsim.api.Subcircuit; -import edu.gatech.cs2110.circuitsim.api.SubcircuitPin; +import edu.gatech.cs2110.circuitsim.api.SubcircuitComponent; import edu.gatech.cs2110.circuitsim.api.SubcircuitTest; import edu.gatech.cs2110.circuitsim.extension.CircuitSimExtension; import edu.gatech.cs2110.circuitsim.extension.BasesConverter; @@ -45,16 +45,16 @@ public void validate(Subcircuit subcircuit) throws AssertionError { } } - @SubcircuitPin(bits=4) + @SubcircuitComponent(bits=4) private InputPin a; - @SubcircuitPin(bits=4) + @SubcircuitComponent(bits=4) private InputPin b; - @SubcircuitPin(bits=2) + @SubcircuitComponent(bits=2) private InputPin sel; - @SubcircuitPin(bits=4) + @SubcircuitComponent(bits=4) private OutputPin out; @DisplayName("pass A")