FreeCore
Library
Parameterized Functions Made
Simple
Learn how to write your own parameterized
functions in 10 minutes!
by
Rune Baeverrud
For this tutorial, we will design a parameterized frequency divider, similar to the div_by_n function found in the Modules section of the FreeCore Library. If you read this tutorial, you will soon realize that the steps required to write your own parameterized functions are very simple. In fact - they are so simple that you may never want to write a non-parametized function ever again!
The advantages of using parameterized functions are obvious. You design your function once - and hopefully you will never have to dig into that function ever again. This is AHDL's answer to object-oriented programming.
Let’s start by looking at a function defined in the old-style non-parameterized way. Study this example carefully, and make sure you understand it fully before you proceed to the next section.
This is a hard-coded frequency divider, having an output Zero going high for every 248’th count. It will count synchronously with SysClk whenever Enable is high. Counter[] starts at 0 after a system power-up. On the next count it is loaded with 247. On the next count again it will be decremented to 246, and will then continue to decrement on every count all the way down to 0, when the cycle starts all over again.
SUBDESIGN div_1 ( SysClk, Enable : INPUT; Count[7..0], Zero : OUTPUT; ) VARIABLE Counter[7..0] : DFF; BEGIN Counter[].clk = SysClk; Count[] = Counter[]; IF Enable THEN IF Counter[] == 0 THEN Counter[] = 247; Zero = VCC; ELSE Counter[] = Counter[] - 1; END IF; ELSE Counter[] = Counter[]; END IF; END; |
Let’s see how we can make a more maintainance friendly variant of this function. For a start, let's replace all the hard-coded values with constants that we define at the top of the file:
CONSTANT DIVIDE = 248; CONSTANT WIDTH = 8; SUBDESIGN div_2 ( SysClk, Enable : INPUT; Count[WIDTH-1..0], Zero : OUTPUT; ) VARIABLE Counter[WIDTH-1..0] : DFF; BEGIN Counter[].clk = SysClk; Count[] = Counter[]; IF Enable THEN IF Counter[] == 0 THEN Counter[] = (DIVIDE - 1); Zero = VCC; ELSE Counter[] = Counter[] - 1; END IF; ELSE Counter[] = Counter[]; END IF; END; |
The above function actually works fine for most values of DIVIDE and WIDTH, but it has a small design flaw in it, caused by a rather unknown 'feature' in AHDL. If you set DIVIDE to 256 while keeping WIDTH at 8, which should be perfectly valid, you will receive a compiler error message for the line:
Counter[] = (DIVIDE - 1);
which says "Number of bits in the group on the left side of equation (8) must be evenly divisible by the number of bits in the group on the right (9)".
Now - why does this happen? If DIVIDE is defined to be 256, then (DIVIDE - 1) evaluates to (256 - 1) = 255 which is perfectly representable by 8 bits. The problem is that the value of DIVIDE requires 9 bits representation. The result of the evaluation will also be 9 bits, even if only 8 bits are really required.
This is a general problem with evaluated expressions. Another example is an expression such as (16300 - 16200) = 100 which would require 14 bits of representation. Fortunately, there is a way around this problem, and this is how you do it:
CONSTANT DIVIDE = 248; CONSTANT WIDTH = 8; CONSTANT DIV_TMP = (DIVIDE - 1); SUBDESIGN div_3 ( SysClk, Enable : INPUT; Count[WIDTH-1..0], Zero : OUTPUT; ) VARIABLE Counter[WIDTH-1..0] : DFF; BEGIN Counter[].clk = SysClk; Count[] = Counter[]; IF Enable THEN IF Counter[] == 0 THEN Counter[] = DIV_TMP; Zero = VCC; ELSE Counter[] = Counter[] - 1; END IF; ELSE Counter[] = Counter[]; END IF; END; |
By defining another constant DIV_TMP holding the value of (DIVIDE - 1), we will no longer be suffering from this number-of-bits problem.
This problem does not work the other way around - a number such as 47 is easily assignable to an 8 bit vector. This particular number will be sign-extended to fill up all the 8 bits required.
Now – let’s complete this tutorial by transforming into a parameterized function. Actually, all we have to do now is replacing the two topmost constant definitions with a parameter section, and the resulting file will look like this:
PARAMETERS ( DIVIDE = 248, WIDTH = 8 ); CONSTANT DIV_TMP = (DIVIDE - 1); SUBDESIGN div_4 ( SysClk, Enable : INPUT; Count[WIDTH-1..0], Zero : OUTPUT; ) VARIABLE Counter[WIDTH-1..0] : DFF; BEGIN Counter[].clk = SysClk; Count[] = Counter[]; IF Enable THEN IF Counter[] == 0 THEN Counter[] = DIV_TMP; Zero = VCC; ELSE Counter[] = Counter[] - 1; END IF; ELSE Counter[] = Counter[]; END IF; END; |
If you instantiate the above function in a higher-level schematic, the parameter dialog box will automatically appear, allowing you to change the parameter values.
To use the function above as a sub-function in a higher-level schematic or AHDL-file, go through the following simple steps to generate a default include file for inclusion in an AHDL file and a default symbol for use in a schematic. While your design window is active, from the pull-down menus in MAX+PLUS II choose:
In the parameters dialog box which appear when you instantiate the above function in a schematic, you can also decide which ports you want to use or not. For example, you may want to mark the Count[] output as unused if you don't need it. If you do mark it as unused - that port will disappear from the symbol in the higher-level schematic.
If you mark an input as being unused, you must give that input a default value. In this case - if we mark Enable as being unused, we should give it a default value of VCC so that our function would still work. We do this by changing the SUBDESIGN block to:
SUBDESIGN div_4 ( SysClk : INPUT; Enable : INPUT = VCC; Count[WIDTH-1..0], Zero : OUTPUT; ) |
There is also another way to deal with unused inputs. By using the USED statement you can check for unused ports and take appropriate actions. For more information on the USED statement, please see my advanced tutorial on parameterized functions: Parameterized Functions - Advanced Features.
We have been using default values for our parameters. Unfortunately - as you may have noticed already - the default values do not show up in the parameters dialog box when instantiating the function in a schematic. Assuming you have already created a symbol, and you want default parameter values to show up, this is how to do it:
The default parameter values you enter in the symbol editor will override the default values in the PARAMETERS section of the AHDL file. The default values in the PARAMETERS section will only be used when the file is compiled as a stand-alone top-level file, like you would normally do during the debug phase.
You could use global project parameters to further parameterize your design. For instance, if you use a parameterized function with a parameter called WIDTH, you could specify WIDTH to be a number like 8, or you could specify it to be equal to a global project parameter, like GLOBAL_WIDTH. Using this method, you would only have to specify the GLOBAL_WIDTH parameter once, and when you change it, it is applied to all the WIDTH parameters for your entire project. The global project parameters are specified in the Assign->Global Project Parameters menu.
I strongly recommend that you learn how to master the AHDL language as the default engine for all your projects. When I first started to use MAX+PLUS II, I found myself drawing a lot of schematics. But I soon found limitations which forced me to use the AHDL language. After that my productivity has increased by a factor of 5 or more. AHDL is much more flexible, and AHDL design files are so much easier to create and maintain than a schematic drawing. Sometimes AHDL files are much easier to read as well. Imagine using schematics to draw a state machine - it could take days - while you could do the same thing in just 10 minutes using AHDL. And how do you draw a counter increasing it's own value by 3 on each clock cycle? In a schematic drawing it's a challenge - in AHDL it's just one line of code. Today - I use AHDL for 95% of the design files I create.
If you really want to, you can also design parameterized functions using schematics. Draw your schematic using parameterized functions like you normally would. But instead of assigning fixed values to the parameters in the functions you use, assign a new parameter name of your choice instead. To create your parameters, use the PARAM primitive found in the MAX+PLUS II library, and optionally assign default values to them. From there, everything else works in the same way as if you had designed your function using the AHDL language.
Last updated 08 Feb 2001 12:10