Sequential Logic in VHDL
Sequential logic remembers state. In an FPGA, this usually means flip-flops, registers, counters, pipelines, and state registers. In VHDL, the standard beginner-friendly shape is a clocked process using rising_edge(clk).
Learning Objectives
You will learn to:
- write a clean clocked VHDL process;
- build registers, enables, and counters;
- choose synchronous reset structure for FPGA work;
- explain signal-update behavior inside a process;
- avoid derived clocks, unsafe resets, and unsynchronized inputs.
Clocked Register
library ieee;
use ieee.std_logic_1164.all;
entity register1 is
port (
clk : in std_logic;
d : in std_logic;
q : out std_logic
);
end entity register1;
architecture rtl of register1 is
begin
process(clk)
begin
if rising_edge(clk) then
q <= d;
end if;
end process;
end architecture rtl;
This describes one flip-flop. On each rising clock edge, q captures the value present at d, subject to real setup and hold requirements.
Clock Enable
process(clk)
begin
if rising_edge(clk) then
if en = '1' then
q <= d;
end if;
end if;
end process;
The register keeps its previous value when en = '0'. This is intentional storage and is different from accidental latch inference in combinational logic.
Synchronous Reset
For many FPGA designs, beginners should start with synchronous reset unless the device, board, or IP block requires otherwise.
process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
q <= '0';
elsif en = '1' then
q <= d;
end if;
end if;
end process;
The reset is sampled by the clock. It is easy to simulate and often maps cleanly to FPGA control resources.
Counter Example
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity counter8 is
port (
clk : in std_logic;
rst : in std_logic;
en : in std_logic;
count : out std_logic_vector(7 downto 0)
);
end entity counter8;
architecture rtl of counter8 is
signal count_r : unsigned(7 downto 0) := (others => '0');
begin
process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
count_r <= (others => '0');
elsif en = '1' then
count_r <= count_r + 1;
end if;
end if;
end process;
count <= std_logic_vector(count_r);
end architecture rtl;
The register is count_r. The output conversion only changes the type view of the same bits.
Signal Updates Inside a Clocked Process
Signal assignments inside a clocked process schedule updates. Think: all selected flip-flops sample their inputs at the same clock edge, then all their outputs update together.
process(clk)
begin
if rising_edge(clk) then
a <= b;
b <= a;
end if;
end process;
This swaps a and b on each clock. It does not first assign a, then immediately use the new a for b.
Idealized Timing
title "Register sampling, idealized"
time start=0 end=40 unit=ns divisions=8
CLK: square label="clk" low=0 high=1 duty=50 cycles=4 unit=logic color=#2563eb
D: square label="d input" low=0 high=1 duty=40 cycles=1 phase=20 unit=logic color=#16a34a
Q: step label="q output after edge" low=0 high=1 at=20 unit=logic color=#dc2626
marker EDGE1 at=10 label="edge"
marker EDGE2 at=20 label="captures"
This waveform is idealized for teaching. Real timing must be checked with static timing analysis, not by reading a drawing.
Reset Choices
| Reset type | VHDL shape | Use when | Risk to watch |
|---|---|---|---|
| synchronous | reset branch inside rising_edge(clk) |
most beginner FPGA logic | reset must meet setup time |
| asynchronous assert, synchronous release | reset in sensitivity list plus synchronized release | board-level reset requirements | unsafe release can create metastability |
| no reset | no reset branch | datapaths initialized by valid control | simulation needs defined startup strategy |
Do not reset every register by habit. Reset control state; reset datapath registers when the design needs a known value.
Worked Example: Rising Edge Detector
library ieee;
use ieee.std_logic_1164.all;
entity edge_detect is
port (
clk : in std_logic;
rst : in std_logic;
signal_i : in std_logic;
pulse_o : out std_logic
);
end entity edge_detect;
architecture rtl of edge_detect is
signal signal_d : std_logic := '0';
begin
process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
signal_d <= '0';
else
signal_d <= signal_i;
end if;
end if;
end process;
pulse_o <= signal_i and not signal_d;
end architecture rtl;
This assumes signal_i is already synchronized to clk. If it comes from a button, connector, sensor, or another clock domain, synchronize it first.
Exercise
Build a 4-bit counter named counter4_tc.
Requirements:
- synchronous reset;
- enable input;
- output
countasstd_logic_vector(3 downto 0); - output
terminal_countbecomes1when the counter equals 15; - explain whether
terminal_countis combinational or registered.
Explained Solution
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity counter4_tc is
port (
clk : in std_logic;
rst : in std_logic;
en : in std_logic;
count : out std_logic_vector(3 downto 0);
terminal_count : out std_logic
);
end entity counter4_tc;
architecture rtl of counter4_tc is
signal count_r : unsigned(3 downto 0) := (others => '0');
begin
process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
count_r <= (others => '0');
elsif en = '1' then
count_r <= count_r + 1;
end if;
end if;
end process;
count <= std_logic_vector(count_r);
terminal_count <= '1' when count_r = 15 else '0';
end architecture rtl;
Here terminal_count is combinational because it is continuously derived from count_r. If another block needs a glitch-free registered pulse, register it inside the clocked process.
Common Mistakes
- Creating a new clock with LUT logic instead of using clock enables.
- Using an unsynchronized external signal in clocked logic.
- Releasing asynchronous reset without synchronization.
- Expecting signal assignments inside a process to update immediately.
- Resetting large datapaths unnecessarily and hurting routing.
- Forgetting
numeric_stdbefore counter arithmetic.
Summary
Clocked VHDL processes describe flip-flops and registers. Use rising_edge(clk), prefer clear synchronous reset and enable structures, keep arithmetic types explicit, and synchronize any signal that is not already in the clock domain.
Next: Finite-State Machines in VHDL.
Further Reading
- GHDL documentation
- AMD Vivado synthesis templates
- Intel Quartus Prime reset and control signal recommendations
- Cliff Cummings papers on nonblocking assignments and synchronous design