The Java Circuit Simulator


In the 'old days' people would design circuits on paper, wire  them up on a 'breadboard', and then test them and modify them till they worked.  That wasn't an efficient way to work at that time, and it's hardly feasible in the current days of VLSI.  Not only are circuits much more complex, but modifying them once they are built is much more difficult.  It has therefore become standard practice to simulate any circuit before it is built.

In this course we will make use of object-oriented programming to create simple yet powerful circuit simulation tools for design and verification.  Specifically, we will use Java, so you will need some Java development environment on your machine (for example, JDK and JCreator as a GUI;  both of these are free).

At its simplest, this tool can be used to connect up a few logic primitives to create a circuit which you can then simulate and test.  However, this is not enough for complex circuits.  These will be built by defining new modules (new objects) in terms of the logic primitives, and then using these in the circuit.  This will gradually create a hierarchy of increasingly complex logic modules, which will finally allow you to build a CPU out of a few components.

The basics:  setting and testing wires

The simplest circuits are made by connecting some basic gates with wires.  Suppose we initially provide you with a 2-input AND gate, in the form of a Java class And2.  To create an instance of an AND gate, with the output connected to wire out and the inputs connected to wires a and b, we would write
new And2 (Sim.xw("out"), Sim.xw("a"), Sim.xw("b"))
The arguments to a constructor for a circuit object and the wires to connect to that object.  Here, xw stands for external wire, meaning a wire which is not internal to a circuit module.  To make this gate part of our circuit and name it "fred", we would write the line
Sim.add ("fred", new And2 (Sim.xw("out"), Sim.xw("a"), Sim.xw("b")));
Initially all wires a set to false (0).  We then let the signals propagate to the gate outputs with the operation;
We can verify that the output of the AND gate is 0 by
System.out.println ("out = " + Sim.get ("out"));
This will print
out = false
We can change the values of the input lines with
Sim.set("a", true);
Sim.set("b", true);
This will change the values of the a and b lines 1 time unit from now.  We then let the signals propagate again with;
This command continues simulation until there are no more changes, or for a maximum of 1000 time units.  [The command;  simulates for n time units.]  We can then check the values of the lines with
System.out.println ("out = " + Sim.get ("out"));
which will will print
out = true
In addition to getting the value of a wire at a particular time, it is possible to 'trace' a wire, which prints a message each time the value of the wire changes.  If we typed the sequence
Sim.trace ("a");
Sim.trace ("b");
Sim.trace ("out");
Sim.set("a", true);
Sim.set("b", true);;
we would see the output
Set a to true at time 1
Set b to true at time 1
Set out to true at time 2

Connecting primitives

Two circuits are connected together by connecting the output of one circuit and the input of another circuit to the same wire.  Thus we can build a three-input AND gate out of two two-input AND gates as follows:
Sim.add ("fred",  new And2 (Sim.xw("z"), Sim.xw("a"), Sim.xw("b")));
Sim.add ("wilma", new And2 (Sim.xw("out"), Sim.xw("z"), Sim.xw("c")));
This circuit sets wire out to the AND of wires a, b, and c.  Wire z is used to connect the output of the first AND gate (fred) to one of the inputs of the second gate (wilma).  A single wire can be connected to several circuit inputs, but only to one circuit output.

Building modules

The power of design 'language' comes from the ability of the programming language to define new circuit modules as new classes.  Suppose we wanted to make the 3-input AND circuit we just designed into a new module, called And3.  In Java, we would define a new class And3 as follows:
class And3 extends Module {
    And3 (Wire out, Wire in0, Wire in1, Wire in2) {
        in (in0); in(in1); in(in2);  out(out);
        addComponent ("and0", new And2(iw("and01"), in0, in1));
        addComponent ("and1", new And2(out, iw("and01"), in2));
Note that this defines a new module type, And3, but does not create any instance of this module.  To create an instance of the module, we would type (in our main method)
Sim.add ("barney",  new And3 (Sim.xw("out"), Sim.xw("a"), Sim.xw("b"), Sim.xw("c")));
This would create an instance of an And3, which in turn creates two instances of And2's.

And3 is an extension of the class Module -- circuits which are defined in terms of their components.  The only method defined is the constructor for instances of And3.  The arguments to the constructor are the wires which are the inputs and outputs of the module.  The constructor in turn calls three methods:  the in method, which specifies that its argument(s) are input wires to the module;  the out method, which specifies that its argument(s) are output wires to the module;  and the add method, which adds a component to the module and gives it a name.

Method iw defines an internal wire -- a wire which is local to that module.  Each instance of that module will contain a separate wire whose local name is and01.

Simulation method

The circuit simulator employs a simple event-driven simulation method.  We maintain a list of wires (NextUpdates) which will change in the future (at the next time unit), and their new values.  The main loop of the simulator advances the time and updates all of the wires with their new values.  It then invokes the update method on every primitive whose inputs have just changed.  The update method of a primitive computes the new values of the output and places these new values on the NextUpdates list.  This process continues until we advance the time and find that there are no wires to update;  at that point the circuit has 'settled'.

Control panel

To make it easier to test new circuits, we provide a circuit control panel which allows you to interactively change any external input wire, examine any external output wire, and run the circuit.  (An input wire is one which is not connected to any circuit outputs.)  You can create such a panel by executing Sim.circuitControl();  after you have created your circuit.