Day 5 · Counters, Shifters & Sync

Counter Variations

Video 1 of 4 · ~12 minutes

Dr. Mike Borowczak · Electrical & Computer Engineering · CECS · UCF

Counter VariationsShift RegistersMetastabilityDebouncing

🌍 Where This Lives

In Industry

Counters are the second-most-instantiated cell in any SoC (flops are first). Refresh counters in DRAM controllers. PC counters in CPUs. Sample counters in DSPs. Baud-rate dividers in every UART. Frame counters in video pipelines. A typical ARM Cortex-A processor has 50+ counter instances across its peripherals.

In This Course

Your Day 5 debouncer uses a modulo counter. Day 7 FSM timers are counters. Day 11 UART baud generator is a modulo counter. Day 12 SPI clock divider is a loadable counter. Today's patterns appear in every remaining lab.

Career tag: “Parameterized counter” is a staple interview question. Know the modulo-N, up/down, and loadable patterns cold.

⚠️ Two Kinds of Wraparound

❌ Wrong Model

“A modulo-N counter is just a free-running counter that I mask with % N. The synthesizer will figure it out.”

✓ Right Model

Two fundamentally different circuits. Free-running: count to 2N-1, wrap via overflow (free in hardware). Modulo-N: count to arbitrary N-1, wrap via explicit comparator + reset. The modulo-N version adds a comparator (~log₂N LUTs) but can count to any value.

The receipt: Verilog % does not synthesize efficiently for non-power-of-2 moduli. You always write the comparator + conditional reset pattern explicitly. The synthesizer builds exactly what you wrote.

👁️ I Do — Modulo-N Counter

module mod_n_counter #(parameter N = 10) (
    input  wire i_clk, i_reset,
    output reg  [$clog2(N)-1:0] o_count,
    output wire o_wrap
);
    always @(posedge i_clk) begin
        if (i_reset)             o_count <= 0;
        else if (o_count == N-1) o_count <= 0;        // wrap
        else                     o_count <= o_count + 1'b1;
    end
    assign o_wrap = (o_count == N-1);                  // pulse at rollover
endmodule
My thinking: $clog2(N) auto-sizes the width — no off-by-one errors. The o_wrap pulse goes high for exactly one cycle per cycle-of-N; perfect for cascading counters or triggering periodic events.

🤝 We Do — Up/Down Counter

module up_down_counter #(parameter W = 8) (
    input  wire i_clk, i_reset, i_up,
    output reg  [W-1:0] o_count
);
    always @(posedge i_clk) begin
        if (i_reset)   o_count <= 0;
        else if (i_up) o_count <= o_count + 1'b1;
        else           o_count <= o_count - 1'b1;
    end
endmodule
Together: What happens when i_up=0 and o_count=0? Rollover to 2W-1 — unsigned wraparound. If you want saturation instead, add a conditional guard.

🧪 You Do — Write the Loadable Counter

Extend the basic counter: synchronous load of i_load_val when i_load=1, otherwise increment.

Answer:
always @(posedge i_clk) begin
    if      (i_reset) o_count <= 0;
    else if (i_load)  o_count <= i_load_val;
    else              o_count <= o_count + 1'b1;
end
Priority: reset > load > count. This is the preload pattern used in timers and PWM period registers.
▶ LIVE DEMO

Modulo-N Counter + Cascade

~4 minutes

▸ COMMANDS

cd labs/week2_day05/ex1_mod_n/
make sim         # exhaustive modulo test
make wave        # see the wrap pulse
make stat        # SB_CARRY count

▸ EXPECTED OUTPUT

PASS: counts 0..N-1
PASS: wraps at N
PASS: o_wrap pulses 1 cycle
=== 20 passed, 0 failed ===

▸ GTKWAVE

Add o_count · o_wrap. Count climbs 0, 1, ..., N-1. At N-1, o_wrap pulses high for exactly one cycle and count resets to 0. The sawtooth pattern is the signature of a modulo counter.

🔧 What Did the Tool Build?

$ yosys -p "read_verilog mod_n_counter.v; synth_ice40 -top mod_n_counter; stat" -q

=== mod_n_counter ===  (N=10, so width = $clog2(10) = 4 bits)
   Number of wires:                 13
   Number of cells:                 15
     SB_CARRY                        3     ← carry chain for +1
     SB_DFF                          0
     SB_DFFESR                       4     ← 4 counter flops
     SB_LUT4                         8     ← comparator logic + combinational
What to notice: 4 flops (counter state) + 3 carry cells (increment) + comparator LUTs (for == N-1). If N were a power of 2, the comparator would vanish — that's the classical iCE40 shortcut: always pick powers of 2 when possible.
Scaling: 32-bit modulo with large N ≈ 32 flops + 31 carry + ~6 LUTs for the comparator. Still tiny.

🤖 Check the Machine

Ask AI: “Write a parameterized Verilog counter that counts from 0 to N-1 and pulses a done signal at the end. Then estimate LUT count on an iCE40 for N=1000.”

TASK

Parameterized counter + LUT estimate for N=1000.

BEFORE

Predict: 10 flops ($clog2(1000)=10), 9 carry, ~5 LUTs for compare.

AFTER

AI should use $clog2. Weak AI hardcodes the width or misses parameter.

TAKEAWAY

Verify absolute numbers with Yosys — AI LUT estimates are often 2× off.

Key Takeaways

 Modulo-N = explicit comparator + conditional reset.

 Use $clog2(N) to auto-size counter width.

 Up/down, loadable: same core + priority-encoded inputs.

 Wrap pulse (1 cycle) is often more useful than the count.

Never write % in synthesizable code. Write the comparator.

🔗 Transfer

Shift Registers

Video 2 of 4 · ~10 minutes

▸ WHY THIS MATTERS NEXT

Counters hold numbers. Shift registers hold streams of bits. Video 2 introduces SIPO and PISO — the two patterns that power every serial protocol. These will be your UART transmitter and receiver in Week 3.