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

The MODGEN Module Generator Library (deprecated)

Modgen has been deprecated and can now be found in contrib.

Technology specific module generators are documented under Logic Descriptions - level 4.

What is a Module Generator?

The module generator libraries distributed with JHDL provide parameterized, complex, hand-placed building blocks which users can leverage to simplify the design of JHDL-based circuits. In this respect they are similar to many other module generator tools and libraries in existence. To date, the set of modules for which generators exist has been driven by our applications work with JHDL. This set will grow in the future.

The list below identifies all of the old modgen modules. They do have some optimized circuitry, especially for XC4000, so they may still used - they are found in the contrib directory. The updated modules now exist in Logic.Modules for technology-independent modules. Also, each technology has its own Modules directory.

Unless noted in their API, these module generators are generally available for use in both the Xilinx XC4000 and Virtex families.

Using Module Generators

To use module generators you simply import the packages named byucc.jhdl.modgen, byucc.jhdl.modgen.fp or byucc.jhdl.modgen.msd and then instance the modules desired.

As a result of this, anyone can add module generators to their design environment - they are simply parameterized circuit designs similar to the parameterized adder example from the Describing Circuits section of the JHDL documentation.

As an example of how to use a predefined module, consider the code required to instance an upcounter from the modgen library:

     new upcnt(this,
               clk_en,    // Clock enable
               load,      // If asserted, load the value stored in load_data
               load_data, // Value to be loaded when load is asserted
               out);      // The output of the up counter

The API Documentation for upcnt can be consulted to determine what kinds of parameters can be passed into its constructor to instance various forms of the up counter.

In addition to the modules found in the module generator, much similar functionality is found in the Logic class. In case you don't find what you are looking for in the module generator library, consult the Logic Class.

In reality, all module generators could have been wrapped into Logic. The advantage of Logic is that you need not import anything to use it - you simply extend it and obtain access to its methods for instancing hardware functionality. The advantage of having a separate library of module generator classes (which you import) is that you can call any static methods they provide. This will be shown in the following section.

Module Generator Support for Numerical Accuracy Studies (Advanced Topic)

A number of the computation-oriented module generators have been written to support behavioral numerical accuracy studies prior to hardware implementation. For example, suppose you are designing a signal processing application for which you know you will be using adders, multipliers, and CORDIC units. Further, assume you don't know at the outset what word sizes will be needed for each intermediate value to give the accuracy you desire.

To determine the word widths needed in your design you could try one of the following:

It would be easiest if you had access to the module generator functionality in a form without timing. This would enable you to ignore the control flow of your design and concentrate on the limited precision functionality of your datapath computation.

Each of the datapath-type modules (addsub, multiply, CORDIC) provides just this functionality. Each contains a static compute() method which can be called from arbitrary Java code and thus used in accuracy studies. Each compute() routine (documented in the module generator API), allows the specification of word widths and other parameters such as signed/unsigned to ensure that the result it gives back is bit-level identical to the hardware module it represents, albeit without any timing provided.

An Accuracy Study Example

As an example of the use of compute() routines, let's use the arrayMult module from the modgen package in a Java program.

Step 1. Define an interface for multiplication, such as:

     interface Mult {
       double doit (double a, double b);
     }

This interface code will allow your Java code to easily switch between a double-precision multiply (based on the "*" operator) and that provided by the compute() method in the arrayMult module.

Step 2. Write a double-precision multiplier class which implements this interface:

     public class DoubleMult
       implements Mult {

       public double doit (double a, double b) {
         double c=a*b;
         return c;
     }
}

This provides the correct double-precision answer for multiplication. You will use this later to compare against the limited precision module you will implement in the next step.

Step 3. Write a fixed-point class which implements the same interface. It will do this via the following steps (which you can identify in the code example below):

  1. Accept double-precision operands and convert them to a fixed point representation
  2. Do the fixed point computation via a call to the arrayMult's compute() method
  3. Convert the result back to double-precision before returning it.
import java.lang.Math;
import byucc.jhdl.modgen.*;
public final class FixedMult implements Mult {

  /* These parameters are used below in converting double values 
     to fixed point value.*/
  double input1_mult, input2_mult, de_divisor;
  int width1, width2;
  boolean signed;


  /* This constructor sets up the interface between the double inputs and 
     integer inputs to the compute methods */
  public FixedMult (int input1_width, int input_bits_before_decimal1,
                    int input2_width, int input_bits_before_decimal2,
                    boolean is_signed) {

    signed = is_signed;
    width1 = input1_width;
    width2 = input2_width;

    /* These multipliers put the needed bits above the decimal for conversion to ints */
    input1_mult = Math.pow(2.0,(double)(input1_width-input_bits_before_decimal1));
    input2_mult=Math.pow(2.0,(double)(input2_width-input_bits_before_decimal2));

    // The divisor converts the final int back into a double
    de_divisor = input1_mult * input2_mult;
  }

  // Here is the routine that does the work
  public double doit(double a, double b) {
    long my_a, my_b;
    long output;
    long round;

    // Convert doubles to ints
    my_a = (long)(a * input1_mult);
    my_b = (long)(b * input2_mult);

    // Do the computation in fixed point by calling the compute() routine
    if (signed)
      output = arrayMult.compute(my_a, width1, my_b, width2, width1+width2, 1);
    else 
      output = arrayMult.compute(my_a, width1, my_b, width2, width1+width2, 0);

    // Convert the result back to a double and return it
    double result = (double)(output / de_divisor);
    return result;

  }

}

A program can now easily be written which performs accuracy studies. Because of the interface created, all the multipliers you create will be of type Mult - both fixed point and floating point. For example, the double-precision module is created with this code:

     Mult my_mult = new DoubleMult();

An 8x8 multiplier to compare to this is created by:

     Mult my_fixed_mult = new FixedMult(8,0,8,0,false);

Then, in your accuracy study code, the call to perform the multiply does not change, but remains as:

     my_mult.doit(input1,input2);

or

     my_fixed_mult.doit(input1,input2);

Using this mechanism, our design style for some signal processing applications has been as follows:

  1. Start with a high level program describing the functionality of the algorithm. Often, this has been given to us as a MATLAB program. Use this program as a baseline for comparison.
  2. Write a Java-based program equivalent to the above program. Double-precision floating point variables are used to ensure the results of this program are identical to those from above.
  3. Incrementally modify a version of the program from #2 above to call compute() routines as shown in the previous example. Using this program, a series of accuracy studies can be done to verify the word sizes needed.
  4. Modify the program from #3 above to be a full JHDL design which instances modules instead of calling compute() routines.
This postpones the development of control logic and timing until the word widths required for the computation have been determined.

Using this approach, the results from #1 vs. #2 should be identical and easily checked. Similarly for #3 vs. #4.

|   JHDL Home Page   |   Configurable Computing Lab   |   Prev (TBone)   |


JHDL 0.3.45