Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
    • Flip-Flops and Registers
    • Counters and Timers
      • Counting as Controlled State Evolution
      • Up/Down Counter with Reload
      • A Prescaler Feeding a Timer Tick
      • Watchdog Timer Example
      • A Gray-Code Counter
      • Mini Project: a Programmable Timer
    • Shift Registers and LFSRs
    • Pipelining
    • Stalls, Bubbles and Hazards
    • Retiming
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Counters and Timers
  1. Topics
  2. RTL Design
  3. Sequential Design
  4. Counters and Timers
  5. Up/Down Counter with Reload

Part 3 · Sequential · Intermediate

Up/Down Counter with Reload

Up/down counting with load and a choice between wrapping and saturating at the limits.

On this page

    Why this matters

    Decrement an unsigned fill-level counter at 0 and it wraps to the maximum, so empty becomes full. Wrap or saturate is a decision about what an overflow should mean.

    After this lesson you can: write an up/down counter with load, enable and direction, order the priority chain, and choose between wrapping and saturating limits.

    Empty must not become full

    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.

    The priority chain

    Input

    Effect

    Priority

    rst_n

    Clear to 0

    Highest

    load

    Load load_val into the counter

    Second

    en & up

    Increment

    Third

    en & !up

    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.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 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 hold

    Wrap or saturate

    A 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

    Both limits in one trace

    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.

    Common pitfall

    Decrementing 0 in an unsigned register wraps to the maximum value, turning empty into full for a fill-level counter. Saturate at the limit, or prevent the illegal decrement and assert it never happens.

    Tip

    A random-stimulus testbench with a plain-integer reference model finds limit and priority bugs that directed tests miss. The reference model stays as simple as possible.

    Check yourself 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.

    Testbench

    Both counter variants plus a testbench that checks them against integer models over random stimulus.

    updown.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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
    

    Remember

    • Priority is reset, then load, then enabled counting.

    • Wrap suits addresses and pointers; saturate suits levels, credits and gain values.

    • Test it against a plain-integer reference model with random stimulus to find limit and priority bugs.

    Keep going

    • Practice: Parameterised up/down counter with wrap or saturate

    • Code lab: Up/down counter

    • Interview: run a mock interview on this topic

    Related topics

    • Memory interfaces use these counters as pointers

    Previous

    68 / 350

    Next: A Prescaler Feeding a Timer Tick

    Up next

    Up nextA Prescaler Feeding a Timer Tick

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Watchdog Timer Example
    • A Gray-Code Counter
    • Mini Project: a Programmable Timer
    clkloadload_val2eupc_sat0210efc_wrap0210fef0
    A 4-bit saturating and a 4-bit wrapping counter on the same inputs: load 2, count down past 0, load e, count up past f; values in hex