module counter_bin #(parameter W = 4) (
input wire clk, rst_n,
output reg [W-1:0] count
);
always @(posedge clk or negedge rst_n)
if (!rst_n) count <= {W{1'b0}};
else count <= count + 1'b1; // wraps naturally at 2^W
endmoduleAn N-bit counter overflows from 2ᴺ−1 to 0, so its period is 2ᴺ cycles. That counter suits power-of-two period lengths.
Other periods reset at the terminal count M-1, the last value before wrapping. A one-cycle pulse on that value marks each period boundary.
M may be 1 (a counter that holds 0 with tc always high): the count width is guarded as localparam int CW = (M > 1) ? $clog2(M) : 1; because $clog2(1) is 0, which would give a [-1:0] count.
The bottom row, tc, is high when count holds M-1 (the module decodes count >= M-1, which is the same thing for every legal count).
Slots 1–5: count runs 0 through 4 and tc stays low.
Slot 6: count is 5 and tc is high.
Slot 7: count wraps to 0 and tc falls.
Slots 8–11: count runs 1 through 4 and tc stays low.
Slot 12: count is 5 and tc is high.
Slots 13–14: count is 0 then 1 and tc stays low.
Mistake | Effect | Fix |
|---|---|---|
Compare to | Period is M+1 | Terminal count is |
Decode |
| Decide whether tc is combinational on count, or registered, and document it |
Use |
| Compare to |
Counter free-runs past a limit it should stop at | Needs saturation, not wrap | See the up/down lesson |
6 of 16 states (37.5%) are unused. The module below does this: it decodes tc = (count >= M-1) (>=, not ==), so a forced out-of-range value such as 12 in a mod-10 instance is treated as terminal and returns to 0 on the next clock, instead of counting through 13, 14 and 15 first. With == the counter would only recover once it had walked round to 0 by itself.
The modulo-M counter plus a testbench that checks four laps and the tc pulse.
module counter_mod #(parameter int M = 6) ( // M must be at least 1
input logic clk, rst_n,
output logic [(M > 1 ? $clog2(M) : 1)-1:0] count,
output logic tc // high while count >= M-1 (equals M-1 in normal operation)
);
// M = 1 guard ($clog2(1) is 0, so [$clog2(M)-1:0] would be [-1:0]).
localparam int CW = (M > 1) ? $clog2(M) : 1;
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) count <= '0;
else if (tc) count <= '0; // wrap after the terminal count
else count <= count + 1'b1;
assign tc = (count >= M - 1); // >= (not ==): a forced out-of-range count still wraps
endmodule