3

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:

  • It looks like the parameter COUNTER_WIDTH is declared in each module (#(parameter COUNTER_WIDTH = 8)), and then "connected to" (if that is the correct expression) the parameter declaration in the other module (e.g. #(.COUNTER_WIDTH(COUNTER_WIDTH) )
  • Is that understanding correct? If so, why do we have to declare the parameter within a module and connect it to a parameter within another module?

Thanks in advance for your help!

McKendrigo
  • 39
  • 1
  • 5

2 Answers2

4

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
Matthew Taylor
  • 13,365
  • 3
  • 17
  • 44
  • Thanks, that clarifies my understanding. Am I correct in saying that the parameters are declared (with a value) in every module to ensure that the module has *some* value to work with, but these are subsequently overridden by connecting the parameters to another parameter in the instance of the other module? – McKendrigo Nov 22 '19 at 10:48
  • Just to follow-up on my own comment after spending time playing around with the code... The parameter values are passed down the heirarchy (from test_fixture, to top_module, to counter), and not up, which makes sense. So, if the counter size is set in test_fixture to, say, 32, and set to 8 elsewhere, you end up with a 32 bit output. If you set the counter size in the counter submodule to 32, and 8 elsewhere, you end up with an 8 bit output. Thanks again for you help, Matthew. – McKendrigo Nov 22 '19 at 11:13
  • @McKendrigo I have edited my answer to add some more info about parameters, eg why they must have default values and what you can "connect" them to (I'd say "associate" them with). – Matthew Taylor Nov 22 '19 at 11:29
1

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.

Oldfart
  • 6,104
  • 2
  • 13
  • 15
  • Thanks for the response, can I just double-check I have understood correctly? In my original post, and also in the first block of code you show above, in top_module the parameter COUNTER_WIDTH is first declared (#(parameter COUNTER_WIDTH = 8), and then this is connected to the parameter of the same name in the counter.v module, which overrides the value to whatever the value is in the counter.v module. In your second example, because it is not connected to the parameter declaration in counter.v, it retains the "default value", which is what was declared earlier. Is that correct? – McKendrigo Nov 22 '19 at 10:40
  • Yes, in the companies I worked the default value would be set by the designer to be the most 'sensible one'. – Oldfart Nov 22 '19 at 10:49