VLSIChaps Academy, Track 01.

Parameters, One Module for Every Size

~12 minHands-on labone exerciseVerilator
Page 17 of 50
01

One Module, Many Sizes

Somewhere right now an engineer maintains a module called sum4 and its near twin sum8, identical except for a size, and every fix must be applied twice. You know where this story goes, because this primer has told it twice already: pasted blocks became one task, pasted definitions became one package, and pasted copies always drift. Modules are where the pattern costs the most, whole files duplicated for want of one number. The parameter is the language's answer: a named knob on the module, so the size stops being written into the code and starts being chosen by whoever uses it. One module, every size, one place to fix.

02

Declaring and Using the Knob

A parameter is declared in the module header with a name and a default, for example a DEPTH of four, and from that point the name replaces the number everywhere the size appears: in the port that carries DEPTH elements, in the loop that walks DEPTH times, in every bound and every count. That everywhere matters more than it sounds. A parameterized module is only as general as its most hardcoded line, and a single leftover literal is a bet that the module will only ever be used at the size you happened to write. This lab is built to collect on exactly that bet.

03

Parameter Versus Localparam

Not every constant should be a knob. A parameter is an offer to the caller: override me at instantiation. A localparam is a decision made inside: a constant, often derived from the parameters, that no caller can touch. The working rule is about intent. If a user of your module could legitimately want a different value, make it a parameter; if the value follows from other choices or must never vary, make it a localparam, so the interface offers exactly the knobs that are safe to turn and nothing more. A module's parameter list is a promise about what may change; keep the promise short and honest, the same discipline that keeps a loop bound legible instead of a maze of magic numbers.

04

Key Insight: the Override and the Leftover

The payoff happens at instantiation: the caller sets the knob, per instance, and one body of code becomes two different machines. In this lab, code you never see will instantiate your module twice, once at depth four and once at depth eight, feed both known data, and check both totals. Here is the insight to carry out of the page: the dangerous mistake is not forgetting the parameter, the tool catches that loudly. The dangerous mistake is parameterizing almost everywhere, the port takes DEPTH, the loop still stops at four, because that module works perfectly at the size you tested and fails only at the size you did not. First instance right, second instance wrong is how this bug ships in real companies, and it is exactly the trap waiting in this lab.

Lab, one summer, two sizes.

The module below sums exactly four elements, and the number four is hardcoded in two places: the port and the loop. Generalize it: add a parameter named DEPTH with a default of four, and replace every hardcoded four so the module sums DEPTH elements at whatever depth its user chooses. Run and submit. The engine instantiates your module twice, at depth four and depth eight, and checks both totals. Leave one hardcoded four behind, and the first instance will pass while the second quietly sums half its data.

The number four appears in exactly two places in the starter, the port and the loop bound, and your job is to make both of them say DEPTH instead. Declare the parameter in the module header with a default of four, then hunt down every literal four in the body. The engine tests two sizes precisely because the leftover four only breaks at the size you did not think about.
Compiling, elaborating, and simulating at two depths...
Correct at both depths. One module, two instances, two sizes, and every element counted in each. Notice what you actually shipped: not a summer of four and a summer of eight, but a summer of any depth, with one body of code and one place to fix forever. The tasks page collapsed pasted blocks, the packages page collapsed pasted definitions, and you have just collapsed the last and largest duplicate: the module itself.
Not quite. Your module passed at depth four and failed at depth eight, and that exact pattern is the lesson of this page: a leftover hardcoded number hides at the size you tested and surfaces at the size you did not. The engine output and the mentor below name the instance, the totals, and the line still carrying the old four.
Elaboration error. Your module did not survive being instantiated at two sizes. Either the DEPTH parameter the engine tried to set does not exist under that name, or a port is still fixed at four elements and could not accept eight. The verbatim error and the mentor below name which, and the fix in both cases lives in the module header.
Simulation timed out. The loop did not make visible progress toward a reachable exit. Check its increment and bound before trying again.
Something went wrong reaching the engine. Try again in a moment.
AI Mentor
Thinking...
📲 Join 20K+ Engineers