|
|
|
As our example, we repeat the example of the NBitAdder from the JHDL Getting Started guide here:
import byucc.jhdl.base.*;
import byucc.jhdl.Logic.*;
public class NBitAdder extends Logic {
public static CellInterface[] cell_interface = {
in("a", "WIDTH"),
in("b", "WIDTH"),
out("sum", "WIDTH"),
param("WIDTH", INTEGER)
};
public NBitAdder(Node parent, Wire a, Wire b, Wire sum) {
super(parent);
int width = a.getWidth();
bind("WIDTH", width);
connect("a", a);
connect("b", b);
connect("sum", sum);
Wire carries = wire(width); // Here are the intermediate carry wires.
for (int i=0; i < width; i++) {
if (i==0)
new FullAdder(this, a.gw(i), b.gw(i), gnd(),
sum.gw(i), carries.gw(0));
else
new FullAdder(this, a.gw(i), b.gw(i), carries.gw(i-1),
sum.gw(i), carries.gw(i));
}
}
}
import byucc.jhdl.base.*;
import byucc.jhdl.Logic.*;
The first few lines in the file are used to import the libraries
required by JHDL. The first line is required - it provides access to
much of the JHDL base functionality. Technically, the second line is
only required if you intend on using the Logic package. Since it is
recommended that you always use Logic as described in Level 2 - The Logic Package you should
include this line as well.
If you intend on doing only technology-independent design using Logic, the two lines above are all that is required. However, there will be cases where you will want access to specific technology libraries. For example, to gain access to the Virtex library of primitives you would do the following as well:
import byucc.jhdl.Xilinx.Virtex.*;
Documentation on what to include for specific FPGA technology
libraries can be
found in
Level 1 - Instantiating Primitive Library
Elements in JHDL.
public class NBitAdder extends Logic {
The line above is the beginning of the circuit module description.
Specifically, it begins a Java class definition. All circuit modules
should be public. By having this class extend the Logic class
you gain access to a wealth of important functions and subroutines to
help you build your circuit as described in Level 2 - The Logic Package. Thus, unless you
understand what you are up to,
make sure your class extends Logic. A good reason to violate
this recommendation is to define your own class which extends Logic and adds
additional capabilities to what is there (convenience functions,
constants, etc). Your circuit modules would
then extend this new class you had created, thereby gaining access to
the original Logic functionality as well as your additions.
public static CellInterface[] cell_interface = {
in("a", "WIDTH"),
in("b", "WIDTH"),
out("sum", "WIDTH"),
param("WIDTH", INTEGER)
};
The lines above declare the ports associated with your circuit module. The first line there is required, don't change it. Even the spelling of cell_interface must remain intact. The middle 4 lines of the code above is where you customize your module's interface. Important - keep in mind that the above is a declaration of the cell's interface. Don't mix that up with the constructor declaration below.
Ports in JHDL can be of three types: in, out, and inout. The use of inout ports is an advanced topic and is documented in the Tri-State section of the User's Manual.
Each port has a name, enclosed in quotes and a width. A simple 1-bit port might be declared like this:
in("clr", 1),
while a 4-bit port would be:
in("qOut", 4),
In our NBitAdder example, we have declared our ports to have a generic
width. This is two-step process. First, the port width is declared
as a quoted expression. Second, any symbolic names in the expression
are declared using the param keyword. Our NBitAdder is a
simple example of this - all ports are the same width ("WIDTH") and
"WIDTH" is an integer parameter.
A more complex example might come from the declaration of a multiplier:
public static CellInterface[] cell_interface = {
in("a", "AWID"),
in("b", "BWID"),
out("prod", "AWID+BWID"),
param("AWID", INTEGER)
param("BWID", INTEGER)
};
Here, the output wire width will be the sum of input wire widths. Other
important points about generic-width ports include:
public NBitAdder(Node parent, Wire a, Wire b, Wire sum) {
The cell constructor is the subroutine called to create an instance of a cell. It
is an arbitrary Java constructor and can take parameters of any type. In
the example above, it takes a pointer to the parent of the cell to be
constructed and three wires (a Wire is a JHDL class which is described
below). To create an instance of an NBitAdder one would call this
constructor with the appropriate parameters.
Here is an example of the declaration of a more complex constructor:
public multiplier(Node parent, boolean signed, String style, Wire a, Wire b, Wire q) {
In addition to the parent node and wires, this constructor also takes
a boolean and a String to indicate the kind of circuit desired.
Since the constructor code is regular Java, it can do anything it sees
fit with these parameters.
As with all Java classes, a JHDL circuit can have multiple constructors with different parameter lists. One common use for this in the JHDL system is to have a constructor that allows you to specify the module's instance name. This is the name that will show up in the schematic and netlist. A second version of the constructor doesn't have this parameter - if you call this the system will generate an instance name for the module for you.
super(parent);
If a circuit class extends any other class , it must call that class's
super constructor as the first thing it does. Be sure it passes on
the parent parameter it received.
In addition, if it desires to specify a String instance name, the following
variation is allowed:
super(parent, instanceName);
This will ensure that instanceName is the name this module goes
by in the schematic viewer and in the netlist.
int width = a.getWidth();
bind("WIDTH", width);
The next step is to bind any generic parameters. In the example
above, wire a is queried to determine its width. That is then
the value which "WIDTH" is bound to. This is important - if you
forget to bind a param JHDL will complain and refuse to build
your circuit.
In reality, the value used to bind to "WIDTH" in this example can be any integer value. However, querying wire a to determine its width seems the only really sensible way to do this.
Finally, you are not required to use two lines of code like above to do this. An equivalent to the above would be:
bind("WIDTH", a.getWidth());
The advantage of the 2-line version is that you now have an integer
variable which holds the width of your wire and will likely need that
information later in your constructor.
The last required item is to connect the wires passed into your
constructor as parameters to the ports on your circuit. This may seem
like a redundant thing to have to do but it is required and is a
by-product of the decision to embed JHDL in 100% vanilla Java. That
is, the cell_interface tells JHDL what the ports on your
circuit should be (name, width). However, JHDL has no way to
figure out which wire was wired to a given port without being
explicitly told that information. The connect() is used to do
that. It associates a wire (this will always be a wire passed in as a
constructor parameter) with a port name like this:
connect("a", a);
connect("b", b);
connect("sum", sum);
The first parameter to connect is a port name (a string). The second
parameter must be of type Wire.
In this example (and most others in the documentation) the name of the port and the name of the wire are always the same. This is strictly coincidental. It would be perfectly legal to do this:
connect("a", someWireThatIWantToConnectToPortA);
Remember - the first parameter is a port name (a string) and the
second is a wire that was passed into the constructor.
Note: Classes that define a JHDL circuit element cannot be an inner class. Java names for inner classes have a '$' included in the cell name, which is an invalid character in a cell name. Such a design will fail when run through the Xilinx tools. You may create inner classes that are "helper classes", but these classes cannot be part of the circuit heirarchy.
Creating a local wire is a two step process. First, you need to declare a wire variable like this:
Wire w;
Since all wires in JHDL inherit from class Wire you can always
just declare your wires to be of that type, regardless of the
technology you are targeting.
Also, remember - this
doesn't actually create the wire object - it just creates a Java variable
suitable for referencing a wire. The second step in the process is to
initialize the declared wire. Though every technology will have its own primitive
wire object and associated constructor (for a Xilinx FPGA the wire
object is called an Xwire) you can make your design apply to them
all by using a platform independent wire (see level 2, the
Logic Package). The Logic package has methods which instantiate and return wires. This is how to create a
4-bit wide wire:
w = wire(this, 4, "wireNameGoesHere");
The last parameter, the String for the wire name is optional. The
second parameter, the width, can be any integer value and can be a
constant like above or an arbitrary Java expression. The above two
steps can be obviously combined like this:
Wire w = wire(this, 4, "wireNameGoesHere");
Remember that a wire must be created before it can be used.
Thus, in our NBitAdder example we created the carry wires before we
instanced any 1-bit adders using them.
A common thing to want to do is to create an array of wires. For example, here is how you would declare an array of wires:
Wire[] wires = new Wire[10];
What does this do? It creates an array of 10 wire references.
However, remember that this only accomplishes step 1 of the 2-step
wire creation process. Each element of the array is null (it
doesn't point to a wire object). To actually make this array of wire references
point to wire objects you would need to do step 2 like this:
for (int i=0;i < 10;i++)
wires[i] = wire(this, 16, "wireNameGoesHereIfYouWant");
Now, each element of the wires array points to an actual 16-bit
wire object which has been created.
If we had given a FPGA specific example, say using XWire, for the above examples, note that the import statements at the
beginnning of the JHDL file would have contained import
byucc.jhdl.Xilinx.Xwire; to indicate to Java the location of
the Xwire class.
Wire a = wire(this, 16);
Wire q = wire(this, 5);
new inverter(this, a.gw(0), q.gw(4));
This will create a 16-bit wire and a 5-bit wire. It will then create
an inverter in the circuit. The input to the inverter will be the
least significant bit of a and the output will be the most
significant bit of q. Thus, the .gw(n) method applied
to a wire variable will access the n-th bit of that wire.
Another common operation is to access a range of bits from a wire. Consider this:
and_o( a.range(3, 0), q.gw(0) );
If the and_o object expected a 4-bit wire as its first
parameter, this code will work by extracting the least significant 4
bits of a, bundling that up as a new wire object, and passing a
reference to that new wire object to the and_o
constructor.
Multi-bit wires are indexed with index 0 being the least significant bit, index 1 being the next and so on. Also note .getWire(n) is another way to doing .gw(n). One name is more descriptive, the other is shorter.
To summarize, the wire accessor methods have the following prototypes. Note that in each case the resulting wire can be named:
Wire a = wire(this, 8);
Wire b = wire(this, a.getWidth()); // Make b the same width as a
Wire c = wire(this, a.getWidth(), "c"); // Make c the same width as a
Wire t;
t = a.getWire(3); // t = a[3]
t = b.gw(5); // t = b[5]
t = c.range(6, 4); // t = c[6:4]
Note that calling wire() created new wires for a,
b, and c. Since t never called the
wire() constructor, it is simply a pointer to the wires
created and returned by the getWire(), gw(), and
range() method calls.
A common error in doing JHDL design would be to do it like this:
...
Wire t = wire(this, 3);
...
t = c.range(6, 4); // t = c[6:4]
Where is the error? Wire t is initially
assigned to point to a new wire of width 3. Later it is made to
point at the wire formed from bits 4 through 6 of wire c.
While this will not make your design simulate or netlist incorrectly
it is wasteful - you have created a 3-bit wire object and then never
used it.
Wire a = wire(this, 2, "a");
Wire b = wire(this, 1, "b");
Wire c = wire(this, 4, "c");
Wire abc = concat(this, a, b, c, "abc");
The wire abc is now a 7-bit wire representing the concatenation
of wires a, b, and c, with wire a
representing the MSBs and wire c representing the LSBs.
Before you are ready to begin JHDL design you need to learn about the
various ways of describing logic in the body of a circuit
constructor. That is the subject of the next section,
Introduction
to Creating Logic Descriptions With JHDL.
| | JHDL Home Page | Configurable Computing Lab | Prev (User's Manual) | Next (Logic Descriptions) | |
JHDL 0.3.45
Copyright (c) 1998-2003 Brigham Young University. All rights reserved.
Last updated on 11 May 2006