A counter is the "hello world" of sequential logic: a flip-flop bank whose
next value is a function of its current value. Every real design is full
of them — timeouts, addresses, dividers, timestamps.
The design is a 4-bit up-counter with the two control inputs that
essentially every register in every design should have:
Synchronous reset (rst): checked at the clock edge, first
priority. (Async-vs-sync reset is a real debate; synchronous is the
simpler default on FPGAs, and reset release must be clean either way —
see the CDC generator's reset synchronizer.)
Enable (en): when low, the counter holds. Enables are how slow
logic lives in a fast clock domain — far better than
dividing the clock.
Watch three things in the waveform: nothing happens before reset
deasserts; the count only advances while en is high; and when the count
hits 4'hF it wraps to 0 — binary overflow is free modulo arithmetic.
Experiment: make it count down when a dir input is high; or make a
BCD counter that wraps at 9 (you'll need an if, not just overflow).
The design
Verilog — design.v
// 4-bit up-counter with synchronous reset and enable.
module counter (
input wire clk,
input wire rst, // synchronous, active high
input wire en,
output reg [3:0] count
);
always @(posedge clk) begin
if (rst)
count <= 4'd0;
else if (en)
count <= count + 4'd1;
// no final else: hold (a register holds by default)
end
endmodule
Show the VHDL version
VHDL — design.vhd
-- 4-bit up-counter with synchronous reset and enable.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity counter is
port (
clk : in std_logic;
rst : in std_logic;
en : in std_logic;
count : out unsigned(3 downto 0)
);
end entity;
architecture rtl of counter is
begin
process (clk) begin
if rising_edge(clk) then
if rst = '1' then
count <= (others => '0');
elsif en = '1' then
count <= count + 1;
end if;
end if;
end process;
end architecture;
The testbench
Verilog — tb.v
`timescale 1ns/1ns
module tb;
reg clk = 0, rst = 1, en = 0;
wire [3:0] count;
counter dut (.clk(clk), .rst(rst), .en(en), .count(count));
always #5 clk = ~clk;
initial begin
$dumpfile("wave.vcd"); $dumpvars(0, tb);
#12 rst = 0;
#10 en = 1; // count 0,1,2,...
#60 en = 0; // hold
#20 en = 1; // resume; runs long enough to wrap F -> 0
#120 $finish;
end
endmodule
Simulated waveform
This trace was produced by actually simulating the code
above with Icarus Verilog.