Verilog Coding Tips and Tricks: Instantiation
Showing posts with label Instantiation. Show all posts
Showing posts with label Instantiation. Show all posts

Monday, November 16, 2015

Synthesiable Verilog code for a 4 tap FIR Filter

Few years back I wrote a VHDL code for implementing a FIR filter. In this post, I want to implement the same algorithm in Verilog.

Finite Impulse Response(FIR) filters are one of the two main type of filters available for signal processing. As the name suggests the output of a FIR filter is finite and it settles down to zero after some time. For a basic FAQ on FIR filters see this post by dspguru.

A FIR filter output, 'y' can be defined by the following equation:



Here, 'y' is the filter output, 'x' in the input signal and 'b' is the filter coefficients. 'N' is the filter order. The higher the value of N is, the more complex the filter will be.

For writing the code in Verilog I have referred to the paper, VHDL generation of optimized FIR filters. You can say I have coded the exact block diagram available in the paper, "Figure 2".

This is a 4 tap filter. That means the order of the filter is 4 and so it has 4 coefficients. The inputs are chosen to be 8 bits wide and outputs are chosen to be 16 bits wide. Both inputs and outputs can store negative numbers, in two's complement format. If you want to handle inputs with bigger range you can simply increase the size of the inputs and intermediate variables. The structure of the code will remain the same.

The design contains two files. One is the main file with all the multiplications and adders defined in it, and another one for defining the D flip flop operation.

The codes are given below:

fir_4tap:

module fir_4tap(
        input Clk,
        input signed [7:0] Xin,
        output reg signed [15:0] Yout
        );
    
    //Internal variables.
    wire signed   [7:0] H0,H1,H2,H3;
    wire signed   [15:0] MCM0,MCM1,MCM2,MCM3,add_out1,add_out2,add_out3;
    wire signed     [15:0] Q1,Q2,Q3;
    
//filter coefficient initializations.
//H = [-2 -1 3 4].
    assign H0 = -2;
    assign H1 = -1;
    assign H2 = 3;
    assign H3 = 4;

//Multiple constant multiplications.
    assign MCM3 = H3*Xin;
    assign MCM2 = H2*Xin;
    assign MCM1 = H1*Xin;
    assign MCM0 = H0*Xin;

//adders
    assign add_out1 = Q1 + MCM2;
    assign add_out2 = Q2 + MCM1;
    assign add_out3 = Q3 + MCM0;    

//flipflop instantiations (for introducing a delay).
    DFF dff1 (.Clk(Clk),.D(MCM3),.Q(Q1));
    DFF dff2 (.Clk(Clk),.D(add_out1),.Q(Q2));
    DFF dff3 (.Clk(Clk),.D(add_out2),.Q(Q3));

//Assign the last adder output to final output.
    always@ (posedge Clk)
        Yout <= add_out3;

endmodule

DFF:

module DFF
        (input Clk,
        input [15:0] D,
        output reg [15:0]   Q
        );
    
    always@ (posedge Clk)
        Q = D;
    
endmodule

Testbench for the FIR filter:

module tb;

    // Inputs
    reg Clk;
    reg signed [7:0] Xin;

    // Outputs
    wire signed [15:0] Yout;

    // Instantiate the Unit Under Test (UUT)
    fir_4tap uut (
        .Clk(Clk), 
        .Xin(Xin), 
        .Yout(Yout)
    );
    
    //Generate a clock with 10 ns clock period.
    initial Clk = 0;
    always #5 Clk =~Clk;

//Initialize and apply the inputs.
    initial begin
          Xin = 0;  #40;
          Xin = -3; #10;
          Xin = 1;  #10;
          Xin = 0;  #10;
          Xin = -2; #10;
          Xin = -1; #10;
          Xin = 4;  #10;
          Xin = -5; #10;
          Xin = 6;  #10;
          Xin = 0;  #10;
    end
      
endmodule

Simulation waveform:

The following waveform was obtained in Xilinx ISE 14.6 after successful simulation.


Synthesis Results:

The modules were successfully synthesised for Virtex 4 device and a maximum frequency of 528 MHz was obtained.


Saturday, October 24, 2015

Module Instantiation methods in Verilog

Every digital design is implemented in Verilog using one or more modules, depending on the size of the design. Once designed, you can think a module as a black box of defined inputs and outputs. How the outputs are derived from inputs, isn't our concern any more. We are concerned only with the functionality of the module.

These smaller modules(or black boxes) can be used to build bigger modules. To use a Verilog module in another one, we have to use Instantiation. By Instantiating we tell the compiler that, how the signals in the bigger module are connected to the smaller(instantiated) module.

There are many ways to achieve this.

Let's say we have a module which looks like this:

module arith(
    A,
    B,
    C,
    D,
    Sum,
    Product
    );

Method 1 : Named Association

    arith uut1 (
        .A(A_in_topmodule), 
        .B(B_in_topmodule), 
        .C(C_in_topmodule),
        .D(D_in_topmodule),
        .Sum(Sum_in_topmodule),
        .Product(Product_in_topmodule)
    );

This is the most common way of instantiating a module. We mention the port names of the instantiated module and the signal names in the top module to which they are connected.

This method is the safest, because there is low chance that we might connect the wrong signals to the ports. Plus, its easy to understand for another programmer, going through the code.

In case you don't want to use some of the ports, we can still use this method.

    arith uut1 (
        .A(A_in_topmodule), 
        .B(B_in_topmodule), 
        .C(),    
        .D(),
        .Sum(Sum_in_topmodule),
        .Product()
    );

Empty parentheses means no signal is connected to that particular port. 

Method 2 : Positional Association

    arith uut1 (
        A_in_topmodule,
        B_in_topmodule,
        C_in_topmodule,
        D_in_topmodule,
        Sum_in_topmodule,
        Product_in_topmodule
        );

This method is a short cut way of instantiating. As the name suggests, the signals in the top modules are connected in the same order as the ports are declared in the instantiated module.

Even though it makes the job easier at the time of coding, it might cost you some time later on. For a second person going through the code, it will take him/her much more time to understand. Plus there is more chance that we might connect the wrong signal to the wrong ports.

In case you don't want to use some of the ports,

    arith uut1 (
        A_in_topmodule,
        B_in_topmodule,
        ,
        ,
        Sum_in_topmodule,
        
        );

Just keep the commas. You really don't need to leave an empty line as shown above. I did it so that, it's more easy for you to understand.

Method 3 : Mixing Implicit naming and Named Association

    arith uut1 (
        .A,
        .B,
        .C,
        .D,
        .Sum(Sum_in_topmodule),
        .Product(Product_in_topmodule)
        );

Suppose some of the signals have the same name as the ports and some of them are different. In the above instantiation, we can see that the first four signals have the same name and so we didn't mention any signal names inside parentheses. For the last two ports, the signal names are different from the port names and so they are mentioned inside the parentheses.

It's up to you, to decide which method to use to instantiate a module. I normally go with named association method. 

So, what's your favorite method of instantiation?