JHDL Module Generator Standards

This document's purpose is to set forth guidelines and standards that should be followed when writing a module generator for JHDL. These standards will help ease the task of maintaining the current library of module generators (the byucc.jhdl.modgen package) and serve as a tutorial to anyone new to the JHDL module generator scene. Thus this document will contain some very basic explanation to help those starting out as well as discussion and standards on how to implement the things that are common to most module generators. As with any standard, there will be exceptions to the rule because of different circumstances from what the standard was designed for, but module generator designers should do there best to follow every applicable standard to make it easier for those who come after. The following is an overview of the topics covered in this document:

 

Module Generator Basics

What is a Module Generator?

A module generator is a design that will build itself according to the configuration parameters that are passed into it. For example, the NBitAdder in the JHDL Documentation Getting Started section is an example of a very simple module generator. Besides the adder size, other parameters could also be passed into the NBitAdder such as a registered parameter or a parameter that indicates if overflow logic should be added to the adder. The ideal module generator would build the most efficiently constructed and placed circuit for the parameters given. Obviously this ideal is not always possible because the the most efficient construction of a circuit can be highly dependant on the application. Although, a very high level module generator could also have a parameter that indicates a certain configuration or placement directive to the module generator. FPGA implementation-independence is another module generator ideal, although this ideal is difficult to acheive. For a little more introduction to the module generators in JHDL see the Modgen Module Generators section of the Users Manual in the JHDL documentation.

Generic width input/output wires

The NBitAdder example from the JHDL documentation shows one way to implement generic wire widths in JHDL by passing the desired widths in as integer parameters to the constructor. It's implementation is seen below:
    public class NBitAdder extends Logic {
      public static CellInterface[] cell_interface = {
        in("a", "width"),
        in("b", "width"),
        out("sum", "width+1"),
        param("width", INTEGER)
      };
  
      public NBitAdder(Node parent, Wire a, Wire b, Wire sum, 
                       int width, String name) {
        super(parent, name);
        bind("width", width);
        connect("a", a);
        connect("b", b);
        connect("sum", sum);
        . . . 
        . . .
        . . .
      }
    }
    
While this method works, it adds a parameter into the constructor for every generic width that is needed in the circuit. The following more efficient way of handling generic wire widths uses the Wire class's method getWidth() to get the width of one of the input wires. This approach has the advantage that the constructor has one or more less parameters. Seen here is a different constructor whose CellInterface is the same as example above:
     public NBitAdder(Node parent, Wire a, Wire b, Wire sum, 
                      String name) {
        super(parent, name);
        int width = a.getWidth();
        bind("width", width);
        connect("a", a);
        connect("b", b);
        connect("sum", sum);
        . . . 
        . . .
        . . .
      }
    

Other Configuration Parameters

Other configuration parameters besides the wires that specify different configurations can be implemented in a number of ways. Virtually any valid java data type can be used as parameters but the most useful are probably integers, booleans or static final variables whose name describes what the parameter indicates. Generally, parameters that have only two options should be represented as booleans, like for example a signed parameter or a registered paramater. When a configuration parameter is not very intuitively mapped into a boolean or an integer, then a public static final variable (a constant) should be used to allow the user to type this variable name in as a parameter. An example is in the Cordic module generator where if one wants a Cordic that uses radians, then the static final variable name Cordic.RADIAN is passed into the appropriate parameter. If an input or output Wire parameter is not required to be used by the user you should allow him to pass a null in for this parameter. You can do this by testing the wire to see if it's null and if it is, removing the port with the removePort(String port) call instead of connecting it. Ideally, if you know that an optional wire is null you should optimize as much logic as possible (i.e. if one of many outputs is passed a null, eliminate any logic that creates that output that is not needed for other signals). Note that depending on the module configuration you may require that a certain input or output wire be null (i.e. the IntDivide module generator requires that if the divider rounds the quotient that the remainder output parameter be passed a null). You can enforce this requirement as explained in the next section.

Valid Parameter Verification

Parameters should be checked for their validity before any logic is made in a module generator to assure correct construction of the module. This should be done for all parameter combinations that are not supported or that just don't make sense given the nature of the circuit. If one of these is found, the building of the circuit should not be allowed to continue. The best way to assure this is by throwing a run-time exception with a meaningful error message if one of these invalid parameter combinations is found. One will want throw either a ModgenException (found in the modgen package) or a private exception class created specifically for that module that extends ModgenException (it is important that one uses a ModgenException or something that extends it since this will provide a JHDL Version number when the Exception is thrown). The advantage of a private exception class is that it cannot be caught and so you are guaranteed that the exception will be a terminating exception when thrown (which is generally what you want). Here's an example of a private exception class that is used in the arrayMult module generator:
    //exception used for invalid parameters into constructor
    private class arrayMultException extends ModgenException  {
      protected arrayMultException(String gripe)  {
        super(gripe);     }   }
    
Here's an example from the constructor of arrayMult for a simple error check on the width of x and y by using the error check (the arrayMult module generator doesn't accept inputs of widths less than 3 bits):
     if (width<3||height<3){
       throw new arrayMultException
         ("Width of input wires x and y must be 3 bits or greater."); }
    

Unique Cell Interfaces

A desirable attribute for a module generator is that if you have two (or more) configurations of a module that are exactly the same the netlist only has contains one instance of them. To guarantee this in JHDL one must bind all parameters in the JHDL CellInterface and then override the method cellInterfaceDeterminesUniqueNetlistStructure() so it returns true. There are a few rules that must be followed for this to be true:
  1. ALL variables that are used to decide the structure of the module MUST be bound in the JHDL CellInterface.
  2. All cells that a module calls must also return true on the aforementioned method.

Multiple Contructors

For convenience multiple constructors can be used to help mantain backwards compatibility and to allow different configurations of a module generator. One must be VERY careful with multiple constructors when returning true for the method cellInterfaceDeterminesUniqueNetlistStructure() because making sure that every parameter is bound correctly can be tricky.

One other issue regarding multiple constructors: each module generator should have the ability for the user to specify or not specify an instance name. This should be accomplished with two constructors for each configuration as in the following example showing the use of two constructors for the arrayMult module generator:

  
  public arrayMult(Node parent, Wire x, Wire y, Wire clk_en, 
                    boolean signed, int pipedepth, Wire pout) {
    this(parent, x, y, clk_en, signed, pipedepth, pout, null);
  }

  public arrayMult(Node parent, Wire x, Wire y, Wire clk_en, 
                   boolean signed, int pipedepth, Wire pout, 
		   String instanceName) {
    super(parent, instanceName);
    ...(connect ports, do error checking and build circuit)

    

Implementation Independence

Ideally, a module generator should be able to work accross multiple FPGA implementations. A good way to partially achieve this goal is by using the Logic class method calls so that simply changing the default techmapper will retarget the design for a new FPGA architecture. Unfortunately, to create efficient designs it is often necessary to use very implementation-specific design organizations and JHDL primitives such as the carry logic in XC4000 or Virtex. If a module generator optimized for one fpga architecture doesn't map well to a different architecture, a switch on the fpga architecture can be used by querying the default techmapper by using the Logic class's static method getDefaultTechMapper and finding out what TechMapper it is using the instanceof operator. Here is an example:
      TechMapper defaultTechMapper = Logic.getDefaultTechMapper();
      if (defaultTechMapper instanceof VirtexTechMapper) {
        set flags here so that a Virtex optimized module is built
      }
      else if (defaultTechMapper instanceof XC4000TechMapper) {      
        set flags here so that a XC4000 optimized module is built
      }
      else {
        set flags so a generic Logic module is built
      }
      

Readable Code

One0 VERY important principle that need to be followed (as it was not followed very well in the past) is to make your code as readable and as simple as possible so that those maintaining the modgen libraries down the road can do so without too much trouble. This includes lots of comments and also longer descriptive variable names (i.e. current_adder could be the iteration variable instead of i in a loop that placed multiple adders side by side so that the code in the loop is a more readable).

 

Placement

As mentioned in the What is a Module Generator section, placement is one of the challenges of writing a generic module generator. It is strongly recomended that when placing that you use relative place calls so that the placement is more readable and more portable to new technologies. It is difficult for the module generator designer to know what the end user will be more interested in, less area or more speed. A good general assumption is that the end user will be interested in both and that you should come up with a good trade-off between area and speed. Although, if it is practical or plausable, you may give a little control to the end user over what the exact configuration of the circuit will be.

A good general rule is to make your design as square as possible so that it can easily be placed with other circuits by the end user. Remember that the origin will be translated to the upper left-hand corner of the bounding box of cell automatically.

 

Behavioral Modeling

To speed up run time, a module generator, wherever possible and practical, should have a behavioral model that models the module's behavior in software. The basic information on behavioral modeling is found in the User's Manual of the JHDL Documentation under the section Behavioral Modeling in JHDL. Remember the following as you write your behavioral model:

 

Automatic Test Benches

One of the great advantages about JHDL testbenches written in java is that automatic and comprehensive tests can be written by simply using the resources available from the Java programming language. An automatic testbench is a testbench that checks its own results and a comprehensive testbench is one that tests all feasable configurations of a module and runs comprehensive (as much as practical) test vectors on that module. Ideally we would like an automatic and comprehensive testbench for every module generator in the byucc.jhdl.modgen package. This would allow a verification of the functionality of every module before a JHDL release and more especially it helps those who modify or maintain your code (including yourself) verify that their changes have not broken anything. An automatic test bench should do the following:

 

Documentation

Documentation for the module generators should be done using javadoc comments and according to the document Documentation Standards for Module Generators. This document should walk you through the basics of documentation with javadoc. Note that the only class from your module generator that needs to be documented in this prescribed way is the top-level class. Remember to make sure that the following are documented:

Implementing a Custom Symbol

Each module should have it's own schematic symbol that will be used in schematic viewer. It many cases it need only be a box with some sort of symbol or text in the box that indicates it's function. To do this, see the Schematic Glyph Interface Instructions.

 

Adding Your Module to the byucc.jhdl.modgen package

For those who are designing for the byucc.jhdl.modgen package, when your module generator is ready to be added to the modgen package you should perform the following steps (note that a module should not be added to this package until it has been thoroughly tested and documented). If your module will reside actually in the modgen package then you should make your top level class part of the modgen package by adding the statement (without the quotes) "package byucc.jhdl.modgen;" to the first line of this file and place that file in the fpga/byucc/jhdl/modgen directory in the CVS repository (see the CVS Tutorial on the internal lab web page and/or ask Carl if you are unfamiliar with CVS).

Any unique helper classes that your top-level file calls and any automatic testbenches should reside in its their own subpackage of modgen. For example the modules that the Cordic module generator calls are contained in the byucc.jhdl.modgen.CordicPack package, implying that they are found in the directory CordicPack found in the modgen directory. Thus you should create a directory in modgen with a name that as well as possible uniquely identifies the purpose of the files contained therein with the word Pack added on the end (no underscores please). Remember that once you've selected a name where your submodules will reside and have made the files part of that package, you will need to import that submodules package in the top-level file in that lives in the modgen package. Once you've put all your files in this new directory and are ready to add this module to the repository do a CVS add followed by a commit on this directory and all the files within it . Add the class files you've added to the modgen Makefile as well as any helper directories you've added. Also when adding a new helper directory make sure there is a Makefile in play (you probably want to copy another helper directory's Makefile) and then you need to make sure that that directory is included in JHDL.jar by modifying the byuc.jhdl Makefile under the MODGEN_CLASSES variable (see Wes if you need more help regarding Makefiles). For modules that don't use any helper classes (like the accum module generator), please make the automatic testbench associated with this module a part of the byucc.jhdl.modgen.modtest package while importing the byucc.jhdl.modgen package and add it to the appropriate directory (fpga/byucc/jhdl/modgen/modtest) in the CVS tree.

For those of you whose top-level module(s) will reside in a seperate directory off the modgen tree it is a good idea to follow the same conventions as if your module was in the modgen directory because although it may not be so now, you may in the future share the directory with another designer and following the above conventions will help maintain organization in the directory structure. The msd directory structure, which is off the modgen package, is a good example of what someone has done in this case.

modgen Package Naming Conventions

Class names should follow Java naming conventions. That is that the first letter is capitalized as well as any new words are also capitalized (no underscores). For example, the modgen class Cordic follows Java conventions but the class arrayMult does not. If modules already commonly in use do not follow this convention, their class names should be left unchanged to assure backwards compatibility.
Russell Fredrickson
Created: Tue Jun 1 10:56:26 MDT 1999
Last modified: Thu Jun 29 03:55:24 MDT 2000