Describing circuits

In order to familiarize you with circuit descriptions in JHDL, this section
will walk through the design of a parameterized n-bit adder, beginning
with the full-adder building block. Each stage will describe an important
aspect of circuit description in JHDL.

The first circuit we will describe is a full-adder.
We will step through this in detail, emphasizing important points.
Here is the code: (FullAdder.java
)

JHDL is a Java-based HDL, and so every circuit in JHDL is an object.
Wires, logic gates, and other circuit building blocks are also objects.
In order to be able to instantiate these JHDL building blocks, we must
import their class files. The first lines in your circuit description should
import the class files byucc.jhdl.base.* and byucc.jhdl.Logic.*.

import byucc.jhdl.base.*;
import byucc.jhdl.Logic.*;

With these class files available, we can now begin to describe our
circuit. Our circuit is an object, so we declare it as a class with:

public class FullAdder extends Logic {

By extending Logic we can have access to methods that will build the gates
in our circuit. For now, always have your circuits extend Logic.

Each circuit you build should have ports (unless you don't ever want to
use it). Ports are used to connect wires to the circuit block when it is
instantiated. These ports are described in the CellInterface. The static
object cell_interface describes an array of these ports.
Each port must be described by a direction, a name, and a width. For example:

in("a", 1)

describes an input port named "a" that is 1 bit wide. Output ports are
described with out(name,width).

Having declared the interface to your circuit, you now must write a
constructor for your circuit (all Java classes have constructors).
The constructor is the method (subroutine) that gets called when your circuit
gets built. Here is the beginning of the constructor:

It is a public subroutine. It takes six parameters.
The first will tell it who its parent in the circuit hierarchy is.
The next five are the wires that should be connected to its ports (a, b,
cin, sum, and cout).

The next line:

super(parent);

is always required. Java programmers will know that this is calling
the constructor of the class this circuit extended (Logic). Non-Java
programmers need not worry - just always do this
and don't ask a lot of questions (you needn't know why this is required).

Next, we must connect the wires that were passed into the constructor
to the ports that were declared in the CellInterface above.
This is done using these lines:

Most cells are useful only if they perform a logic function on the inputs
and pass the result to the outputs. Once wires are connected to the ports
of the cell, the only thing left is to describe the logical function of
the cell. This is done in the next two lines of our constructor:

Our cell's logic is described with calls to Logic methods
and(),
or_o(),
and xor_o(). The first line above performs an or on the
outputs of three 2-input and gates and returns the output to cout. The
second line performs an xor on three inputs and puts the result on the
output wire sum.

Note that the Logic class provides two types of methods to instantiate
gates:
gate() and gate_o(). For instance, we use
and()
calls inside the or_o() call.
gate_o() and gate()
differ only that the output wire is passed to gate_o(), whereas
gate()
returns a newly created output wire. It is important that we use the _o
methods to connect logic to outputs. Otherwise our output wire pointer
will be replaced with a new wire created by the gate() method and
no logic will be connected to the output.

We're now done. The above circuit description is complete.

To review, the steps in the above code were the following:

Import the required packages.

Declare your circuit as a class which extends Logic.

Declare the cell interface (ports).

Begin the constructor definition for the circuit.

Call super(parent).

Do connect() calls to hook the constructor parameter wires up to the ports.

Insert your logic using calls such as or_o() and and().

Note that all of our CellInterface names and Wire names match, but matching
names are not required.

CVT, DTB, and the Simulation Environment

Before continuing on with your JHDL desing, make sure that your
development environment is properly set up. Running through a
basic Java tutorial and properly
setting your CLASSPATH should be
sufficient to verify that your system is properly set up.

With your circuit description complete, you can now simulate your circuit
using the JHDL Circuit Visualization Tool (CVT). First, let's compile
your circuit. On a UNIX machine it is as simple as this:

javac FullAdder.java

This simply calls the Java compiler to compile your circuit. You
are now ready to run. Type this to load your circuit in CVT:

java dtb FullAdder

This will start a new Dynamic Test Bench (dtb), which will load your
circuit and create a new CVT system to view it. The resulting CVT
window should look like this:

Once you have loaded a circuit you will see a tree appear in the left
hand pane of the CVT window. You can use this tree to traverse the hierarchy
of the circuit. As you select cells, you will notice that the wire names,
widths, type, and current value of their ports are displayed in the right
hand pane of the CVT window. You will note that your circuit is the
FullAdder icon. Click the plus sign to the left of it and you should
see this:

What you see is that FullAdder contains some gates of various types.

Now, click on the FullAdder icon itself so that the name FullAdder is
highlighted in color. From the Cell menu at the top of
the window, select View Cell Schematic. (The shortcut methods
are to either select the "FullAdder" icon and press "Ctl-S" or just
double-click on the FullAdder icon.) A new window which
contains a schematic of your adder should pop up like this:

Click on the Cycle in the main CVT window button. The circuit
simulator program initializes
and the values on the schematic are updated to reflect the current values
of those wires in the simulator (currently everything is zero - not very
interesting).

Click in the command box in the middle of the CVT window and type:
put a 1. Then click the cycle button and the window
looks like this:

As you can see, the value of a was updated in the schematic as
well as the computed sum output value. You can use commands
such as these to put values on any inputs and see the results.

Now, let's use the waveform viewer. Close the schematic window
by selecting the Close command from the File menu. Back
in the main CVT window, click on the FullAdder icon in the tree viewer
so that its name in the TreeViewer is highlighted.

What you see is a representation of the ports on your FullAdder cell.
The next column (Width) tells the port width,
the next tells its direction, and the next its current value in the simulator.
Click on the line of the table for port a then select the
Watch wire command under the Wire menu. (The shortcut
methods are to either select the a port and press "Ctl-W" or
just double-click on the port in the table.) This causes the
following window to appear:

This is the waveform viewer window. Without closing this new
window, go back and select the other wires in the wires table of the
main CVT window and select the Watch wire command from the
Wire menu. Then, click the cycle button 3 times and switch
back to the waveform viewer window.
You should get this:

As you can see, signal a is high and signal sum is high.
Now, set b high by typing put b 1 in the command box in
the main CVT window. Cycle once. Then, set cin high
and cycle similarly. You should see something like this in the
waveform viewer (you may need to click on the zoom in or zoom out
button to get a similar view):

From the display it should be clear that the adder seems to be working.
Now, close the waveform window and go back to the main CVT window.

The Command Line Interpreter (CLI) Console

CVT also provides a text-based command console. The Console
is found in the upper panel of the CVT window. The Console contains a
listing of all the commands you have performed. Bring the CVT window to
the front and look at the history. There is a command corresponding
to all of the functions that can be done in the GUI. You can type
"help" to see a list of commands or "help
<command>" to get help on a particular command. By
executing the log command, a file with your command history
can be saved. During a later session, this file can
be sourced at the console by typing "source <filename>".

Here is the console from this session:

You can continue to put values on wires from this window and continue
to simulate.

Netlisting

After a design has been verified using simulation, JHDL makes it possible
to netlist the design. The style of netlist will depend on the target platform.
Generally, it will be a hierarchical EDIF netlist.
You create a netlist
by typing the following command in the command entry window:

netlist -insertpads t -f FullAdder.edn

This tells it to first automagically insert I/O drivers for the
top-level ports on your circuit (input ports get IBUF's, output ports
get OBUF's). It then tells it to generate EDIF and write that into the
file FullAdder.edn.

Dynamic TestBench (DTB)

It turns out that JHDL designs can be executed by writing a programmatic
testbench which will build your circuit, step the clock, send stimulus
into your circuit, and check the outputs for correctness. These
JHDL testbenches are similar to those often written when doing VHDL design.

For simple circuits like the one shown above, users may not want
to write a full-blown, custom testbench. Instead, as shown in that example,
they can use the dynamic
testbench (DTB) provided with JHDL. DTB acts like a testbench
and builds the circuit for you. At this stage of your learning, you
need not be concerned with DTB. However, the JHDL User's Manual
contains sections on both DTB (it has many options associated with it)
and the writing of custom testbenches. [NOTE: DTB documentation is
still in the works.]

Back-end Tools

After creating the EDIF netlist, you will need to run the FPGA vendor's tools
which convert an EDIF netlist to a bitstream. In case of Xilinx, these might be
the Alliance or Foundation tools. At any rate, the execution
of these tools is outside the scope of this JHDL getting started guide
and will be specific to your FPGA site setup.

A More Complex Example - An N-Bit Adder

This cell shows other important points of Logic, including wires and parameters.

The n-bit adder circuit description is called
NBitAdder.java.
Notice that this cell also imports important classes, calls
super(parent)
in its constructor, and defines an interface. However, it has a number
of new features over the previous example:

The ports in this design are parameterized to be any width desired.
Thus, the inputs are declared to be of width "WIDTH". This
means that their width is to be determined by their actual size at
runtime (this is like a generic in VHDL).
The param() line tells the
system that "WIDTH" is an integer.

Inside the constructor is another change:

int width = a.getWidth();
bind("WIDTH", width);

It is required that WIDTH be bound to a value before any connect() calls
are made. Thus, the lines above call the getWidth() function
associated with wire a and assign that value to "WIDTH". After
that, connect calls can be made as in the previous example.

This design is going to build a n-bit wide adder by calling FullAdder
n
times. In order to connect the FullAdder cells together within a
cell, we must instantiate a wire object for the intermediate carries. The
Logic class (which this class extends) provides a method to create wires.
It is called in our constructor with this:

All wires have a gw() function associated with them which allow
you to get the individual bits. This function is used to pull
the individual bits out of the n-bit wires we are dealing with.

Notice that we pass
this as the first parameter to the
FullAdder constructor. this is a pointer to the current
object. Remember how we said above that the FullAdder had to know
who its parent was in the circuit hierarchy? This is how we inform
it of that.

If statements can be used to treat each stage of the n-bit
adder differently.

Note how the intermediate carry wire bits are used to connect cin
to cout between the FullAdder cells. This is a case where
wire local to the current cell are created and used. Since
the carry wires don't enter or exit the current cell on ports they are
simply created inside the constructor and used to wire together the
adder cells as they are instanced.

Continuing - Using the N-Bit Adder

With the above adder created, let's now create a simple accumulator
circuit using it. This will be an 8-bit accumulator. It will consist of three pieces:

The accumulation is done in a register bank consisting of 8 flip flops.

The output of the register is looped back around to an adder. On
the way, however, it is AND-ed with a clear signal. When clear is
high, the AND gates will feed zeroes to the adder. When clear is
low, the AND gates will just pass the register output on.

An 8-bit adder adds this feedback signal to the incoming data and
the output of the adder is fed into the register from above.

import byucc.jhdl.base.*;
import byucc.jhdl.Logic.*;
public class accum extends Logic {
public static CellInterface[] cell_interface = {
in("clr", 1),
in("a", 8),
out("sum", 8),
};
public accum(Node parent, Wire clr, Wire a, Wire sum) {
super(parent);
connect("clr", clr);
connect("a", a);
connect("sum", sum);
// An accumulator is simply an adder,
// a register bank,
// and an AND gate that forces zero on the feedback path
// Here is the output wire for the adder
Wire addOut = wire(8, "addOut");
// Here is the feedback from the register to add back in
Wire feedback = wire(8, "feedback");
// Create an instance of our adder
new NBitAdder(this, a, feedback, addOut);
// Build the actual accumulator register out of 8 flip flops
// The reg_o() call works for any width wires
reg_o(addOut, sum);
// To clear the adder, take the outputs of the register and
// pass them through an AND gate. When "clr" is high, make
// the AND gate outputs be zero.
// We will use a for-loop to build these
for (int i=0;i<8;i++)
and_o(not(clr), sum.gw(i), feedback.gw(i));
}
}

After reading through this, you hopefully realized that it is a simple
thing to make this
accumulator parameterized just like the adder. Here is the modified design:

import byucc.jhdl.base.*;
import byucc.jhdl.Logic.*;
public class NBitAccum extends Logic {
public static CellInterface[] cell_interface = {
in("clr", 1),
in("a", "WID"),
out("sum", "WID"),
param("WID", INTEGER),
};
public NBitAccum(Node parent, Wire clr, Wire a, Wire sum) {
super(parent);
int wid = a.getWidth();
bind("WID", wid);
connect("clr", clr);
connect("a", a);
connect("sum", sum);
// An NBitAccumulator is simply an adder,
// a register bank,
// and an AND gate that forces zero on the feedback path
// Here is the output wire for the adder
Wire addOut = wire(wid, "addOut");
// Here is the feedback from the register to add back in
Wire feedback = wire(wid, "feedback");
// Create an instance of our adder
new NBitAdder(this, a, feedback, addOut);
// Build the actual NBitAccumulator register out of flip flops
// The reg_o() call works for any width wires
reg_o(addOut, sum);
// To clear the adder, take the outputs of the register and
// pass them through an AND gate. When "clr" is high, make
// the AND gate outputs be zero.
// We will use a for loop to build these
for (int i=0;i < wid;i++)
and_o(not(clr), sum.gw(i), feedback.gw(i));
}
}

A careful comparison of the two files will show that only minimal
differences exist. Thus, in general, it is not much harder to make a
design parameterized in JHDL than to make one with hard-coded wire
widths.

Summary

What has been shown are the following points:

To create a JHDL circuit you create a Java class for that
circuit.

JHDL circuits have interfaces consisting of ports.

A set of libraries exist to make circuit creation
straightforward.

Ports can be declared to be of generic width, supporting the
creation of parameterized designs.

For-loops and the rest of the power of the Java language is
available to use in instancing logic.

Once a circuit has been created it is easily used in higher-level
designs. Thus, design usually proceed through the creation of a
collection of simple building blocks (built out of gates). These
building blocks are then combined in higher levels of hierarchy to
create a complete circuit.

This bottom-up building block approach is encouraged by JHDL's
structure.

Where To From Here?

This is the end of the Getting Started Guide. The
JHDL User's Manual will provide a far
more detailed description on the use of the
various tools.for more detailed information on the various tools.
Specifically, it will attempt to fill in many holes about how
circuits are really built in JHDL and the more than 1,000 Logic and
Logic.Modules calls available to help you design circuits. Thus, be
sure to carefully read through those sections before embarking on a
project - many of the simpler building blocks you need likely already
exist!

In addition, the JHDL User's
Manual covers the operation of the range of tools associated with
the JHDL system for design simulation and netlisting.