19 / 37 · Concept
parameter, localparam, $clog2 and boundary configurations
Separate run-time inputs from static configuration and design counter widths and termination for N=1 and non-power-of-two values.
Lessons are free to read. Enroll to save your learning progress.
Parameters are fixed when the circuit is elaborated
A port is an input that can change while a circuit operates. A parameter configures an instance before simulation or synthesis builds its structure. localparam names a derived constant that callers do not override. None of these is a software variable you change every cycle.
For a modulo-N counter, valid states are 0 through N−1. Storage width must represent those states. For N≥2, $clog2(N) gives the required unsigned width. N=1 needs special treatment because $clog2(1) is 0; use a minimum storage width of one bit. N=0 is outside this module’s contract.
Define completion by an enabled edge
This module produces a registered pulse when an enabled edge wraps the old count from N−1 to 0. Disabled edges hold count and clear pulse. Reset clears both and wins over enable. N must be a positive integer within the implementation’s supported integer range.
module modulo_tick #(
parameter integer N = 5,
parameter integer CW = (N <= 1) ? 1 : $clog2(N)
) (
input logic clk, rst, enable,
output logic [CW-1:0] count,
output logic pulse
);
localparam logic [CW-1:0] LAST = CW'(N-1);
always_ff @(posedge clk) begin
if (rst) begin
count <= '0;
pulse <= 1'b0;
end else begin
pulse <= 1'b0;
if (enable) begin
if (count >= LAST) begin
count <= '0;
pulse <= 1'b1;
end else count <= count + 1'b1;
end
end
end
endmoduleCW is exposed because it appears in the port list. Leave its default unchanged, or supply a value at least max(1,$clog2(N)). A smaller override truncates LAST and violates the contract. A larger override adds unused encodings; this implementation recovers from them on an enabled edge because the terminal comparison uses >=.
CW'(N-1) is a constant size cast. It documents the width at the boundary. It does not make an invalid N or CW valid. Validate configuration before elaboration; a run-time assertion cannot repair a malformed declaration.
Test a configuration matrix
| N | Default CW | Count after enabled edges following reset | Pulse after those edges |
|---|---|---|---|
| 1 | 1 | 0, 0, 0 | 1, 1, 1 |
| 2 | 1 | 1, 0, 1 | 0, 1, 0 |
| 5 | 3 | 1, 2, 3, 4, 0 | 0, 0, 0, 0, 1 |
| 8 | 3 | 1, 2, 3, 4, 5, 6, 7, 0 | 0, 0, 0, 0, 0, 0, 0, 1 |
N=1 is not a two-state counter just because it has one storage bit. An N=5 counter must not visit 5,6,7 during normal operation. A power-of-two test alone will not expose a missing explicit wrap comparison.
Practice · Verify behavior and reject bad configuration
Build separate instances for N=1,2,5,8. Run at least two full periods, insert disabled edges at the terminal count, and assert reset at the same time as enable. Check each intermediate count and pulse, not just the final state.
Write an integer reference: on reset, count=0 and pulse=0; otherwise pulse=0, and on enable increment modulo N while detecting wrap. Keep it independent of the DUT’s CW and encoded state. Also test the configuration validator’s rejection of N=0 and undersized CW.
A parameterized module is reusable only over the parameter range you specify and verify. Passing one default instance does not establish every possible width or count limit.