Loading header...

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 count as std_logic_vector(3 downto 0);
  • output terminal_count becomes 1 when the counter equals 15;
  • explain whether terminal_count is 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_std before 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

Mind Map

mindmap root((VHDL Sequential)) Core concept Registers store state rising_edge samples data Enables hold value Reset defines start Applications Counters Pipelines Edge detectors Control registers Formulas Tclk covers Tco logic route setup skew Fmax about 1 over Tclk Setup before edge Hold after edge Design rules Use one real clock Prefer clock enable Reset control state Synchronize external inputs Practical checks Simulate reset Check timing report Review CDC paths Watch terminal count glitches Common mistakes Derived clocks Async reset release Immediate signal update myth Missing numeric_std