|   JHDL Home Page   |   Configurable Computing Lab   |   User's Manual (TOC)   | Search

Overview

Each JHDL circuit file is enclosed in a "cell." In order to interface with other circuit elements the cell's interface ("cell_interface") must be described. The interface lists the input and output ports into and out from the cell. Types of cell ports include in, out and inout. An additional cell_interface parameter "param" declares the cell parameters (variables.)

Once the circuit's cell_interface is finished, it's time to create the circuit's constructor, a method which will create an instance of the circuit.

In the constructor, you'll first need a call to super(). Next, it's necessary to bind values to the previously declared cell_interface "params." Finally "connect" passed-in constructor parameters to cell_interface ports (the in/out/inout from above.)

Once the overhead has been set up, one can then create the circuit's logic by combining logic elements and tying them together with Wires.

Anatomy of a Cell Declaration

In this section we discuss how to declare a new circuit module. We focus mainly on importing required libraries, the cell interface declaration (its ports), and its constructor. In the case of the constructor we do not discuss how to fill in the logic that implements the cell in question. That is in the next few sections of the Users Manual (beginning with Introduction to Creating Logic Descriptions With JHDL).

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));
      }
    }
  }

Importing Libraries

     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.

Declaring a Circuit Module as a Java Class

     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.

Cell Interface Declarations

    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:
  1. The generic port width expression must be enclosed in double quotes.
  2. The generic port width expression can be an arbitrary, parenthesized arithmetic expression involving any declared param's, integer constants, and the usual arithmetic operators (-, +, *, /, **). The ** operator is exponentiation.
  3. The param must also be declared in the manner shown above. The possible types for a param are INTEGER, BOOLEAN, STRING and LONG corresponding to their respective Java types.

Cell Constructors

     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.

Beginning the Constructor Body - Calling super()

 
      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.

Bind and Connect Calls

      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.

The Constructor Body

Once the above has been completed, the code in the rest of the constructor routine is used to build the circuit's logic by calling constructors for wires and other cells. Any Java language constructs desired can be used to aid in this process. Exactly how to do this using the JHDL libraries and module generators is the topic of the next few sections in the Users Manual starting with Introduction to Creating Logic Descriptions With JHDL. Thus, this will not be described in any further detail here.

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.

Wires

Errors in JHDL can commonly occur with the creation of wires. Remember - a wire is an object. In a typical circuit module constructor there are two kinds of wires you will encounter. The first are the Wire parameters passed into the constructor. These wires were created outside the current cell - someone else created objects for them and passed references to those objects into the constructor for the circuit's use. The second type of wires are local wires. These are wires which you create for use inside the cell you are constructing. In the NBitAdder example from Getting Started, the carry wires were local wires.

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.

Slicing and Dicing Wires

If a wire has multiple bits you will want to be able to get at those bits either individual or in groups. Consider the following:
      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:

In addition, as we have already seen in this document the following method is available to get the width of a wire: Here is another example of how to use the bus splitting calls:
     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.

Merging Wires

Sometimes it is necessary to concatenate several wires together to form a bus. The Logic concat() method can take a list of 2 to 16 input wires, each of arbitrary width, and concatenate them into a single bus. The first wire parameter occupies the most significant position in the bus, and the last wire parameter occupies the least significant position. Here is an example:
     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.

Where To Go Next?

In this section the declaration of a JHDL circuit as a Java class has been outlined along with its cell port interface and constructor method. In addition, the declaration and creation of wires have also been described along with a collection of routines for slicing, dicing, and merging them.

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