Coding for Reuse Course - Module 1 Lesson 6
Parameterized Modules

In this lesson we will study parameterized modules in detail and develop the multiplexers for our microprocessor.

1. General  

The most important thing to remember about parameters is that they are a VALUE that is referenced by a KEYWORD. The compiler has limited means for evaluating the value of a parameter; and will frequently generate additional logic, with subsequent delays when the current value of a parameter is used as opposed to a compiler directive. The major exceptions are parameters that control the logic size, such as the depth or width of your function; parameters used to reference state definitions in state machines; and parameters used to setup fixed, reconfigurable structures, such as PLLs and DLLs.

Parameterized modules are generally used for generic, optimized, common functions such as registers, counters, RAM, etc. Frequently parameterized modules are replaced by vendor libraries. These libraries, in turn, may use parameterized modules. In fact, up until a few years ago, there was an industry recognized concept of "library of parameterized macros" or LPMs for certain functions (some very advanced).

Vendor library modules are not generally reusable. Therefore, you should construct your own module that can be transported to any device architecture. A properly designed parameterized module can be synthesized by any software package.

2. Detailed Analysis of a Parameterized Module  

Let us examine "dreg_r" from Lesson 5 in some detail. The first thing you will notice is that this is a conditional compile from the

`ifndef DREG

statement. Generic functions are used in many files such as test benches or peripherals you may develop over time. Most compilers, synthesizers, and simulators will croak if they see the same module redefined. The "`ifndef" statement can prevent this; but THERE ARE SOME CAVEATS.

  1. In keeping with Rule 1.13, extreme care must be used when naming your definition and module. Also, if using a generic, un-altered version of a specific function, be sure the definition and module names match in all included files.

  2. If you test bench requires the same generic function as the device-under-test, aka DUT (your module) it must take the following steps to ensure that the version of that generic function included in your project is used for simulation:

    1. Do not include the module source within the main test file unless a different module and definition name are used.

    2. Instantiate the DUT before any other module that performs functions other than interconnect.

    If you do not use these precautions, it could cost hours of debugging time (i.e. chasing ghosts) if the simulation failure occurs in the version of a generic module within the DUT.

  3. The definition name used for the conditional compile must be defined before the end of the if statement. Hence the statement:

    `define DREG 1

The module which we are examining features two parameters. The 'WIDTH' parameter defines the bit width of the function. The 'RESET_VALUE' parameter sets the state that the function will assume upon power-up or asynchronous reset. These parameters MUST be explicitly declared within a parameterized module before any 'defparam' statement or named parameter passing has any effect.

Since parameters are an integer value, they may be used within mathematical equations; such as the statement:

input [(WIDTH -1)]:0] d;

They may be also used within other parameters either as the value (called aliasing) or as part of the value equation. Parameter values are evaluated first, so they can also be used in "`define" statements. A word of caution, however. The parameter value must be resolved before it can be used within a "`define'. In other words, a parameter used in one "`define" cannot rely on a conditional compile from another "`define". Some synthesizers do not support the use of a parameter in a "`define" statement.

The "RESET_VALUE" sets the state the function assumes on power-up or asynchronous reset events. However; it must be noted that this state may be a "downstream" event. While some device architectures support both an asynchronous preset and reset, many do not. Instead, the compilers for these type of devices generally use "De Morgan's theorem" to invert the output state of that element at compile time and apply subsequent "not gate pushbacks" for synchronous elements. Therefore, in-depth simulations may not yield the expected results when observing an element directly.

When you are performing a conditional compile on any self-contained module, make sure that both the module declaration and the "endmodule" statements are included between the "`if" and "`endif" statements.

3. Basic Multiplexer  

A multiplexer, called a 'mux' for short, is one of the simplest forms of programmable logic. It is a pure sum of products, and is widely used. Any logic can be constructed using only multiplexers. Most state machines use muxes for state transitions. So why do I even choose to discuss them?

Let us look at a standard mux. A standard mux selects one of two signals ('A', 'B') to propagate to the output ('Q') based upon the state of the select signal ('sel') as such:

The standard mux requires the fewest resources and incurs the least routing 'costs' than any other construct when you are dealing with two inputs that are controlled by a third input. Each bit requires only one logic element within modern programmable devices. It is also easy to understand and simulate. Almost every project I have dealt with requires a standard mux. So let us add a parameterized version of our standard mux to our microprocessor source code:

Skeleton with Parameterized Mux

If this mux is ideal, why do we need another one? The answer is simple. When we exceed two inputs using conventional muxes, we must implement a mux "tree". Example:

8 to 1 mux tree

We can see from this example is very expensive in terms of logic element usage (11), logic levels (3), and routing 'costs'.

Before we proceed further, let me explain routing 'costs' for those of you who have never routed a circuit. Multi-layer printed circuit boards and the metal layers in a programmable device are very similar in nature. Basically one layer is devoted primarily to horizontal routes, and the other to vertical routes forming a matrix. At each junction of a vertical and horizontal segment you may switch layers; at a price. In PCBs this becomes a source of electromagnetic interference (EMI). In programmable devices, this become a source for routing delays, since the layer change generally requires an electronic switch. In both cases, the layer switch also generates an impedance mismatch, limiting maximum speed due to reflections and the subsequent inter-symbol-interference (ISI). If most or all of a line is used to avoid those problems, the that line as well as any layer crossings to that line from the paired layer are also blocked.

Autorouters work by assigning a price to each scenario, and attempt to minimize the total cost. Each possible problem is assigned some cost or number of points. Since it is virtually impossible to test every possible combination of placement and routing, a variety of different algorithms are used. If this field of research interests you, I suggest that you make a career of it; exploring it further via education or other available resources.

4. Enhanced Multiplexer  

Another way of avoiding the routing problems and logic delays can be used with multiple enable muxes. Since we are using 4-input logic elements, we can add a separate enable for each input as such:

This mux still fits within one logic element in modern programmable devices, and requires more routing resources when you are dealing with only two inputs. However, when we substitute our enhanced mux into our 8 to 1 mux tree, we get this:

8 to 1 enhanced multiplexer

This example cuts our logic element usage to 6, and our logic levels to two. The trade-off is that we must route two more select signals, so smaller multiplexers lose this benefit. Since our microprocessor uses several different registers and devices for source data; the benefit exceeds the cost. We will add a parameterized version of our enhanced mux to the source:

Skeleton with Parameterized Standard
and Enhanced Mutiplexers

There are a couple of extremely important things to note within the added module. First and foremost, this module can also be modelled with a 'always' block. However, when using an 'always' block to model combinatorial logic, you MUST use a blocking assignment (Rule 6.10). Otherwise you are more susceptible to cascade effects within both simulators and compiles. Second, make sure that the sensitivity list is complete ( Rule 6.11). Third, some combinatorial logic will form a priority encoder; as it is within this case. Make sure that when priority encoders are used, it is consistent with your design intent. Last, the enhanced mux does not completely replace the standard mux. It requires additional logic to generate the added select signal, and incurs added routing costs for this signal.

To avoid the issues involved with using an 'always' block, I have, instead, used a 'generate' block. a 'generate' block is essentially a ' for' loop that creates logic. In other words, each iteration of the loop creates a new instance of the logic within the loop. The 'genvar' controls the loop iterations, but can also be used as a variable within the loop to select which product terms (pterms) are used in a particular instance. Each instance name is created by appending the value of the 'genvar' to the partial name succeeding the 'begin :' part of the "for" statement.

When properly used, the enhanced mux can replace such items as several 74<x>244 buffers driving a common bus, SSI multiplexers with enables, etc.

Exercise  
  1. What is a parameter?

  2. Where do we use parameters in place of a 'define' statement?

  3. Why don't we use the vendor library?

  4. Why do we use a conditional compile for some parameterized modules?

  5. What it called when one parameter uses another parameter as its value?

  6. What can possibly happen when we asynchronously reset a 4-bit register to the value 4'b0101?

  7. What is the short name for a multiplexer?

  8. What consumes the fewest resources in logic and incurs the least routing costs when we must select one of two signals based upon the state of a third signal?

  9. Explain routing costs.

  10. I have two internal signals driving a bi-directional bus. The input signal from this bus is used within the logic that develops these signals. Which multiplexer should use?

  11. Extra credit. Create a paramaterized module that creates multiple instances of the following logic :

    assign q = a ^ b;