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;
`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
Show the VHDL testbench
VHDL, tb.vhd
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity tb is end entity;
architecture sim of tb is
signal clk : std_logic := '0';
signal rst : std_logic := '1';
signal en : std_logic := '0';
signal count : unsigned(3 downto 0);
begin
dut : entity work.counter port map (clk=>clk, rst=>rst, en=>en, count=>count);
clk <= not clk after 5 ns;
process begin
wait for 12 ns; rst <= '0';
wait for 10 ns; en <= '1'; -- count up
wait for 60 ns; en <= '0'; -- hold
wait for 20 ns; en <= '1'; -- resume, wraps F -> 0
wait for 120 ns; std.env.stop;
end process;
end architecture;
The MyHDL version keeps the design and its
testbench in one design.py.
Simulated waveform
This trace was produced by actually simulating the code
above with Icarus Verilog.
Try it live
Open this lesson in a playground, edit the code, and re-run it.