McKendrigo
McKendrigo

Reputation: 49

Passing parameters between Verilog modules

I fairly new to Verilog and learning the ropes. I have some code which generates an 8 bit up-counter (module counter.v), which is then called by a top module (top_module.v). There is a simulation test fixture (test_fixture.v) which calls the top module for testing.

I was trying to define the width of the counter using a parameter (parameter COUNTER_WIDTH) and was having difficulty doing so. A colleague fixed the code for me and it does now indeed work, but I want to understand a few things so I can understand what is actually going on.

Here is the code for the counter module:

module counter
#(parameter COUNTER_WIDTH = 8)
(
    input wire CLK,
    input wire RST,
    input wire CE,
    output reg[COUNTER_WIDTH-1:0] out = {COUNTER_WIDTH{1'b0}}
    );


   always @(posedge CLK) begin
      if (RST == 1) begin
         out <= {COUNTER_WIDTH{1'b0}};
      end else begin
      if (CE == 1) begin
         out <= out + 1'b1;
      end
    end
  end
endmodule

The top module:

module top_module
#(parameter COUNTER_WIDTH = 8)
(
    input wire CLK,
    input wire CE,
    input wire RST,
    output wire[COUNTER_WIDTH-1:0] out
    );

counter #(
    .COUNTER_WIDTH(COUNTER_WIDTH)
    )
counter_inst(
    .CLK(CLK),
    .RST(RST),
    .CE(CE),
    .out(out)
);

endmodule

And the test fixture:

module test_fixture();

parameter COUNTER_WIDTH = 8;

// inputs
reg CLK = 0;
reg RST = 0;
reg CE = 0;

// outputs
wire [COUNTER_WIDTH-1:0] Q;

// instance of top module to be tested
top_module #(
    .COUNTER_WIDTH(COUNTER_WIDTH)
) 

test_inst(
    .CLK(CLK),
    .RST(RST),
    .CE(CE),
    .out(Q)
);
endmodule

I think I'm fine with the counter module, but have a question about what is going on in top module/test fixture:

Thanks in advance for your help!

Upvotes: 4

Views: 10013

Answers (2)

Matthew
Matthew

Reputation: 14007

Think of a parameter as a special kind of constant input whose value is fixed at compile time. Originally, in Verilog, parameters were constants that could be overridden from outside a module (using the now deprecated defparam statement). So, a Verilog parameter had two roles:

  1. a local constant
  2. a way of customising a module from outside

Then, in the 2001 version of the standard (IEEE 1364-2001), the syntax you're using below was introduced (the so-called "ANSI style"). Also IEEE 1364-2001 introduced localparams (which fulfill the first of these two roles because they cannot be overridden from outside), so leaving parameters to handle the second of these two roles (the customisation). With the ANSI-style syntax, you override a parameter when you instantiate a module. You can associate the parameter with any static value, for example a parameter of the parent module, a localparam, a genvar or a literal (a hard-coded value in your code).

Because of this historical dual-role, in Verilog you must give a parameter a default value, even if it makes no sense. (And this restriction is lifted in SystemVerilog.)

Does giving the parameters different names and default values help your understanding?

                //    the default value
                //          |
module counter  //          V
#(parameter COUNTER_WIDTH = 1)
(
    input wire CLK,
    input wire RST,
    input wire CE,
    output reg[COUNTER_WIDTH-1:0] out = {COUNTER_WIDTH{1'b0}}
    );


   always @(posedge CLK) begin
      if (RST == 1) begin
         out <= {COUNTER_WIDTH{1'b0}};
      end else begin
      if (CE == 1) begin
         out <= out + 1'b1;
      end
    end
  end
endmodule

Then, in top module, let's give the parameter a different name:

                   //    the default value
                   //          |
module top_module  //          V
#(parameter COUNTER_NUM_BITS = 2)
(
    input wire CLK,
    input wire CE,
    input wire RST,
    output wire[COUNTER_NUM_BITS-1:0] out
    );

counter #(
    .COUNTER_WIDTH(COUNTER_NUM_BITS)
    )
counter_inst(
    .CLK(CLK),
    .RST(RST),
    .CE(CE),
    .out(out)
);

endmodule

And again in the test fixture:

module test_fixture();

localparam COUNTER_SIZE = 8;   // This is not overridden from outside, so using
                               // a localparam would be better here.
                               // (A localparam being a kind of parameter that
                               //  cannot be overridden from outside. Normal 
                               //  languages would call it a constant.)    
// inputs
reg CLK = 0;
reg RST = 0;
reg CE = 0;

// outputs
wire [COUNTER_SIZE-1:0] Q;

// instance of top module to be tested
top_module #(
    .COUNTER_NUM_BITS(COUNTER_SIZE)
) 

test_inst(
    .CLK(CLK),
    .RST(RST),
    .CE(CE),
    .out(Q)
);
endmodule

Upvotes: 5

Oldfart
Oldfart

Reputation: 6269

why do we have to declare the parameter within a module and connect it to a parameter within another module?

Because, you don't HAVE to give a parameter a value when you instance the module. In which case the tools need to have at least some value to work with.

So where you do:

counter #(
    .COUNTER_WIDTH(COUNTER_WIDTH)
    )
counter_inst(
    .CLK(CLK),
    .RST(RST),
    .CE(CE),
    .out(out)
);

You may also use:

counter
counter_inst(
    .CLK(CLK),
    .RST(RST),
    .CE(CE),
    .out(out)
);

In which case the default value is used.

Upvotes: 1

Related Questions