Video 1 of 4 · ~12 minutes
Dr. Mike Borowczak · Electrical & Computer Engineering · CECS · UCF
The microwave that knows “30 seconds” means 30 seconds. The metronome ticking in a piano app. The blinking cursor in this editor. The 60Hz refresh of your monitor. Every WiFi slot, every USB heartbeat, every audio sample at 48kHz — somewhere underneath, something is keeping score, wrapping back to zero, and starting again.
Dhahran, February 1991: a Patriot battery missed an incoming Scud. Twenty-eight soldiers died. The intercept math relied on a tally that had been running for 100 hours and had drifted by a fraction of a second. The 2015 Boeing 787 “reboot every 248 days” advisory was the same family of bug: a number that ticks up forever, in a place its designer thought would always be reset first.
“A modulo-N counter is just a free-running counter that I mask with % N. The synthesizer will figure it out.”
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.
%: Verilog % by a non-power-of-2 synthesizes to a divider — a multi-cycle, multi-LUT, multi-carry-chain monster. On iCE40, count % 10 can cost 30–60 LUTs and miss timing at moderate clock rates. An explicit comparator + conditional reset costs ~4 LUTs and meets timing easily. You always write the comparator pattern; never the %.
A modulo-N counter is a counter that takes values from $\{0, 1, 2, \ldots, N-1\}$ and wraps back to $0$ on the cycle after reaching $N-1$. It cycles forever through exactly $N$ distinct states.
Required width: to represent $N$ distinct values we need
$$W \;=\; \lceil \log_2 N \rceil \;=\; \texttt{\$clog2}(N)$$
bits. The SystemVerilog primitive $clog2(N) computes the ceiling of the base-2 log at elaboration time — no off-by-one bugs.
Examples: $N{=}10 \Rightarrow W{=}4$ (since $2^4{=}16 \ge 10$); $N{=}1000 \Rightarrow W{=}10$; $N{=}500{,}000 \Rightarrow W{=}19$.
o_count, a 1-cycle o_wrap pulse at the rollover, and (optionally) an enable input i_enable so the counter can be paused.
o_count == N-1 and forces the next state to $0$. Everything else — enable, reset — is just steering logic on the register's D input.
module counter_mod_n #(parameter N = 10) (
input wire i_clk, i_reset, i_enable,
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 (i_enable && o_count == N-1) o_count <= 0; // wrap
else if (i_enable) o_count <= o_count + 1'b1;
end
assign o_wrap = i_enable && (o_count == N-1); // pulse at rollover
endmodule
$clog2(N) lets the elaborator size the bus — no hand-counted bit widths, no off-by-one errors when $N$ changes. The o_wrap pulse goes high for exactly one cycle per cycle-of-$N$; perfect for cascading counters or triggering periodic events.
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
i_up=0 and o_count=0? Rollover to 2W-1 — unsigned wraparound. If you want saturation instead, add a conditional guard.
Extend the basic counter: synchronous load of i_load_val when i_load=1, otherwise increment.
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
We've name-dropped “PWM period register” in a few decks now. Here's what one actually looks like — and why the loadable counter you just wrote is the period register.
i_load_val port of your loadable counter is the period register; it sets $N$. A separate duty register and comparator then split each period into a high-time and low-time. Change the period register → frequency changes. Change the duty register → brightness/speed changes.
Not the full peripheral — just the three pieces that turn the previous diagram into Verilog. The middle block is literally the loadable counter you just wrote, with the wrap event playing the role of i_load.
// 1) Period & duty registers — software-loadable (CSR-style)
reg [W-1:0] period, duty; // period = N-1; duty = high-time
always @(posedge i_clk) begin
if (i_we_period) period <= i_wdata;
if (i_we_duty) duty <= i_wdata;
end
// 2) Period counter — same priority chain as the previous slide.
// The "load" event is the natural end-of-period wrap.
reg [W-1:0] count;
always @(posedge i_clk) begin
if (i_reset) count <= 0;
else if (count == period) count <= 0; // ← reload @ wrap
else count <= count + 1'b1;
end
// 3) Duty comparator drives the PWM output
assign o_pwm = (count < duty);
period sets $N$ (frequency), duty sets the high-time (brightness), count is the loadable counter sweeping 0..N-1. Topic 10's PWM lab is this skeleton plus bus glue.
~4 minutes
▸ COMMANDS
cd lecture_examples/week2_day05/d05_s1_ex1/
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.
$ yosys -p "read_verilog day05_ex01_counter_mod_n.v; synth_ice40 -top counter_mod_n; stat" -q
=== counter_mod_n === (N=10, so width = $clog2(10) = 4 bits)
Number of wires: 14
Number of cells: 15
SB_CARRY 2 ← carry chain for +1
SB_DFF 0
SB_DFFESR 4 ← 4 counter flops
SB_LUT4 9 ← comparator logic + combinational
== 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.
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.
① 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.
% in synthesizable code. Write the comparator.🔗 Transfer
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.