A fill-level counter sits at 0 and one more decrement arrives. In a 4-bit unsigned register, 0 − 1 is 15, so the block that was empty now reports full. Nothing in the hardware is broken; the counter simply wrapped where it should have stopped.
An up/down counter therefore needs two decisions written down before any RTL: the priority between its control inputs, and what happens at each limit.
Input | Effect | Priority |
|---|---|---|
| Clear to 0 | Highest |
| Load | Second |
| Increment | Third |
| Decrement | Third |
none | Hold | — |
The order of the if/else if branches is the priority. Reset clears, load wins over counting, and enable with direction selects increment or decrement.
// snippet
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) count <= '0; // 1. reset
else if (load) count <= load_val; // 2. load beats counting
else if (en) begin // 3. enabled counting
if (up) begin
if (!(SAT && at_max)) count <= count + 1'b1;
end else begin
if (!(SAT && at_min)) count <= count - 1'b1; // SAT = 1: stop at 0
end
end // otherwise holdA wrapping counter rolls past its limit to the other end. A saturating counter stops at the limit.
Wrapping suits addresses and pointers. Saturating suits levels, credits, and gain values, where a wrap would be a serious error.
Counter type | At maximum, count up | At minimum, count down | Typical use |
|---|---|---|---|
Wrapping | → 0 | → maximum | Addresses, pointers, modulo timers |
Saturating | stays at maximum | stays at 0 | Credits, fill levels, volume, PWM duty |
Each slot is one clock cycle. An input drawn in slot k takes effect at the edge that opens slot k+1, so the counts always show the result of the slot before.
Slots 0 to 3: load with load_val = 2 in slot 0, then up low: both counters read 2, 1, 0 in slots 1 to 3.
Slot 4: one more decrement at 0. The saturating counter holds 0; the wrapping counter jumps to f. That is empty becoming full.
Slot 5: load is high with e (the wrapping counter already reads e, one step down from f).
Slots 6 to 8: both read e, then f after one increment. One more increment at f: the saturating counter holds f and the wrapping one returns to 0 in slot 8.
load and en are both high in the same cycle. What happens to the count, and why?The counter loads. load appears before en in the priority chain, so it wins. Whether that is the right priority is a specification decision, so write it down and test it.
Both counter variants plus a testbench that checks them against integer models over random stimulus.
module updown #(
parameter int W = 4,
parameter bit SAT = 1'b1 // 1: saturate at the limits, 0: wrap
) (
input logic clk, rst_n, en, up, load,
input logic [W-1:0] load_val,
output logic [W-1:0] count,
output logic at_max, at_min
);
assign at_max = &count; // all ones
assign at_min = ~|count; // all zeros
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) count <= '0;
else if (load) count <= load_val;
else if (en) begin
if (up) begin
if (!(SAT && at_max)) count <= count + 1'b1;
end else begin
if (!(SAT && at_min)) count <= count - 1'b1;
end
end
endmodule