![]() |
|
|
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