Tutorial: VHDL Modeling of D Flip-Flops, D Latches, and Shift Registers#
In this tutorial, we will learn how to model and simulate basic storage elements in VHDL, such as:
D Flip-Flops#
A D flip-flop (DFF) is a storage element that stores one bit.
On a specific logic transition of the clock input CLK
),
either rising edge or falling edge, the DFF captures the value at its data input (D
) and updates its output (Q
) accordingly.
There are two types of DFFs:
- Positive-edge triggered: updates on the rising edge of the clock.
- Negative-edge triggered: updates on the falling edge of the clock.
The following is an example of a D flip-flop modeled in VHDL.
-- File: d_ff.vhd
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
ENTITY d_ff IS
PORT (
clk : IN STD_LOGIC; -- clock input
d : IN STD_LOGIC; -- data input
q : OUT STD_LOGIC -- data output
);
END d_ff;
ARCHITECTURE behavioral OF d_ff IS
BEGIN
update_proc : PROCESS (clk)
BEGIN
-- use either rising_edge(clk) or falling_edge(clk)
IF rising_edge(clk) THEN
q <= d;
END IF;
END PROCESS;
END behavioral;
To simulate the VHDL model of the DFF, a VHDL testbench is provided.
-- File: tb_d_ff.vhd
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
ENTITY tb_d_ff IS
END tb_d_ff;
ARCHITECTURE sim OF tb_d_ff IS
-- DUT port signals
SIGNAL t_clk : STD_LOGIC := '0';
SIGNAL t_d : STD_LOGIC := '0';
SIGNAL t_q : STD_LOGIC;
-- LFSR reg
SIGNAL lfsr : STD_LOGIC_VECTOR(7 DOWNTO 0) := (OTHERS => '1');
-- Clock period constant
CONSTANT CLK_PERIOD : TIME := 20 ns;
BEGIN
-- Instantiate the D Flip-Flop
uut: ENTITY work.d_ff(behavioral)
PORT MAP(
clk => t_clk,
d => t_d,
q => t_q
);
-- LFSR process
lfsr_proc: PROCESS
BEGIN
-- LFSR feedback (bit 7 XOR bit 5)
lfsr <= lfsr(6 DOWNTO 0) & (lfsr(7) XOR lfsr(5));
WAIT UNTIL rising_edge(t_clk);
END PROCESS;
t_d <= lfsr(0);
-- Clock generation: 20 ns period (50 MHz)
clk_gen: PROCESS
BEGIN
t_clk <= '0';
WAIT FOR CLK_PERIOD/2;
t_clk <= '1';
WAIT FOR CLK_PERIOD/2;
END PROCESS;
END sim;
Note
- In this VHDL testbench, a 8-bit LFSR (Linear Feedback Shift Register)
is used to produce a pseudo-random bit sequence for the data input
d
of the DFF for testing purposes. - This 8-bit LFSR is initialized with a non-zero value (e.g., all bits set to
'1'
). - It shifts the 8-bit register one position to the left on every rising edge of the clock.
- The new bit entering at the LSB position (bit 0) is computed as the XOR of bit 7 and bit 5 of the current value in the LFSR register.
- LFSRs are commonly used in testbenches to simulate randomized input patterns without needing manually specified input vectors.
- The 8-bit LFSR can generate a maximum-length sequence of bits.
- The feedback taps of the LFSR (in this case: bit 7 and bit 5) form a so-called primitive polynomial.
If GHDL is used as the VHDL simulator, the following commands can be used to compile the code and run the simulation:
# Analyze (compile) the VHDL design and its testbench
ghdl -a d_ff.vhd tb_d_ff.vhd
# Elaborate the VHDL testbench (build the simulation model)
ghdl -e tb_d_ff
# Run the simulation, generate VCD waveform, stop at 1 μs
ghdl -r tb_d_ff --vcd=./wave.vcd --stop-time=1us
For .vcd
waveform visualization, the GTKWave program can be used:
# Open the waveform file using GTKWave (in the background)
gtkwave wave.vcd &
The simulation result is shown as a waveform below.
- It shows that the input stimulus signal
d
changes synchronously, immediately after the rising edges of the clock. - Initially, the output
q
of the D flip-flop is in an unknown state ('U'
). - After the first rising clock edge, the output changes to
'0'
, matching the value of the inputd
at that clock transition. - In this example, the output
q
of the DFF is allowed to change at the rising edges ofclk.
Figure: Simulation waveform of the DFF.
The following VHDL code shows how the model of a DFF can be modified to include multiple control signals, including:
- Asynchronous clear
- Asynchronous preset
- Asynchronous load
- Synchronous clear
- Synchronous load
- Data enable and load
This model prioritizes asynchronous controls over synchronous ones and only loads data on the rising edge of the clock when enabled.
-- File: d_ff_ctrl.vhd
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
ENTITY d_ff_ctrl IS
PORT (
clk : IN STD_LOGIC;
d : IN STD_LOGIC;
en : IN STD_LOGIC;
async_clr : IN STD_LOGIC;
async_preset : IN STD_LOGIC;
async_load : IN STD_LOGIC;
async_data : IN STD_LOGIC;
sync_clr : IN STD_LOGIC;
sync_load : IN STD_LOGIC;
sync_data : IN STD_LOGIC;
q : OUT STD_LOGIC
);
END d_ff_ctrl;
ARCHITECTURE rtl OF d_ff_ctrl IS
SIGNAL q_reg : STD_LOGIC := '0';
BEGIN
PROCESS (clk, async_clr, async_preset, async_load)
BEGIN
-- Highest priority: Asynchronous clear
IF async_clr = '1' THEN
q_reg <= '0';
-- Asynchronous preset
ELSIF async_preset = '1' THEN
q_reg <= '1';
-- Asynchronous load
ELSIF async_load = '1' THEN
q_reg <= async_data;
-- Synchronous logic
ELSIF rising_edge(clk) THEN
IF en = '1' THEN
IF sync_clr = '1' THEN
q_reg <= '0';
ELSIF sync_load = '1' THEN
q_reg <= sync_data;
ELSE
q_reg <= d;
END IF;
END IF;
END IF;
END PROCESS;
q <= q_reg;
END ARCHITECTURE;
This model prioritizes asynchronous controls over synchronous ones and only loads data on the rising edge of the clock when enabled. The synthesis result provided by Intel Quartus Prime Lite software is given below.
Figure: RTL-level synthesis result targeting the MAX-10 FPGA
RS Latches#
An RS latch is a basic type of latches which can stores one bit of data.
It has two active-low inputs, reset (/R
or R_N
) and set (/S
or S_N
)
and two complementary output, Q
and QB
, respectively.
An RS latch can be constructed using two cross-coupled two-input NAND gates. The following VHDL code shows how to model an RS latch, with two implementation styles.
-- File: rs_latch.vhd
-- RS Latch with Active-Low Inputs
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
ENTITY rs_latch IS
PORT (
R_N : IN STD_LOGIC; -- Active-low Reset input
S_N : IN STD_LOGIC; -- Active-low Set input
Q : OUT STD_LOGIC; -- Output Q
QB : OUT STD_LOGIC -- Inverted output (Q - bar)
);
END rs_latch;
-- dataflow implementation
ARCHITECTURE impl_1 OF rs_latch IS
SIGNAL q_int, qb_int : STD_LOGIC;
BEGIN
-- Cross-coupled NAND gates
q_int <= NOT(S_N AND qb_int);
qb_int <= NOT(R_N AND q_int);
Q <= q_int;
QB <= qb_int;
END impl_1;
-- Behavioral implementation
ARCHITECTURE impl_2 OF rs_latch IS
SIGNAL q_int : STD_LOGIC := '0'; -- Internal Q signal
SIGNAL invalid : STD_LOGIC := '0';
BEGIN
PROCESS (R_N, S_N, q_int)
BEGIN
invalid <= '0';
IF R_N = '0' AND S_N = '1' THEN
q_int <= '0'; -- Reset: Q=0, QB=1
ELSIF R_N = '1' AND S_N = '0' THEN
q_int <= '1'; -- Set: Q=1, QB=0
ELSIF R_N = '1' AND S_N = '1' THEN
-- no change (hold previous state)
ELSE
invalid <= '1';
q_int <= '1'; -- Invalid state: both active
END IF;
END PROCESS;
-- Output assignments
Q <= q_int;
QB <= NOT q_int WHEN invalid = '0' ELSE q_int;
END impl_2;
VHDL testbench for simulation purposes:
-- File tb_rs_latch.vhd
-- Test bench for RS latch
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
ENTITY tb_rs_latch IS
END tb_rs_latch;
ARCHITECTURE sim OF tb_rs_latch IS
COMPONENT rs_latch
PORT (
S_N : IN STD_LOGIC;
R_N : IN STD_LOGIC;
Q : OUT STD_LOGIC;
QB : OUT STD_LOGIC
);
END COMPONENT;
SIGNAL t_S_N, t_R_N, t_Q, t_QB : STD_LOGIC;
-- LFSR reg
SIGNAL lfsr : STD_LOGIC_VECTOR(7 DOWNTO 0) := (OTHERS => '1');
BEGIN
-- Instantiate RS latch (UUT = Unit Under Test)
uut: ENTITY work.rs_latch(impl_1)
PORT MAP(
S_N => t_S_N,
R_N => t_R_N,
Q => t_Q,
QB => t_QB
);
-- 8-bit LFSR
lfsr_proc: PROCESS
BEGIN
lfsr <= lfsr(6 DOWNTO 0) & (lfsr(7) XOR lfsr(5));
WAIT FOR 10 ns;
END PROCESS;
t_S_N <= lfsr(0);
t_R_N <= lfsr(1);
END sim;
Commands for simulation:
# Analyze (compile) the RS latch design and its testbench
ghdl -a rs_latch.vhd tb_rs_latch.vhd
# Elaborate the testbench (build the simulation model)
ghdl -e tb_rs_latch
# Run the simulation, generate VCD waveform, stop at 1 μs
ghdl -r tb_rs_latch --vcd=./wave.vcd --stop-time=1us
# Open the waveform file using GTKWave (in the background)
gtkwave wave.vcd &
Figure: Simulation result
D Latches#
A D latch is a 1-bit storage element that allows its output to change when the enable input is high. When the enable input is low, the output remains unchanged.
Because D latches are level-sensitive, their output (Q
) follows
the data input (D
) as long as the enable signal (EN
) is active.
If a clock signal is used directly as an enable for a latch,
the output may change multiple times during a single clock cycle if the input toggles.
A D latch can be constructed based on an RS latch or NAND2 gates as shown below.
Figure: Gated D latch
The following is a VHDL code example of a RS latch, implemented using three different styles.
-- File: d_latch.vhd
-- D Latch Entity
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
ENTITY d_latch IS
PORT (
D : IN STD_LOGIC; -- Data input
EN : IN STD_LOGIC; -- Enable input
Q : OUT STD_LOGIC; -- Output Q
QB : OUT STD_LOGIC -- Inverted output (Q-bar)
);
END d_latch;
-- Structural implementation using RS latch
ARCHITECTURE impl_1 OF d_latch IS
COMPONENT rs_latch
PORT (
R_N : IN STD_LOGIC;
S_N : IN STD_LOGIC;
Q : OUT STD_LOGIC;
QB : OUT STD_LOGIC
);
END COMPONENT;
SIGNAL s_n, r_n : STD_LOGIC;
BEGIN
s_n <= NOT (D AND EN);
r_n <= NOT (NOT(D) AND EN);
-- Instantiate RS latch
rs_inst : ENTITY work.rs_latch(impl_1)
PORT MAP(
R_N => r_n,
S_N => s_n,
Q => Q,
QB => QB
);
END impl_1;
-- Behavioral implementation
ARCHITECTURE impl_2 OF d_latch IS
SIGNAL q_int : STD_LOGIC := '0';
BEGIN
PROCESS (D, EN)
BEGIN
IF EN = '1' THEN
q_int <= D;
-- else: latch holds previous state
END IF;
END PROCESS;
Q <= q_int;
QB <= NOT q_int;
END impl_2;
ARCHITECTURE impl_3 OF d_latch IS
SIGNAL q_int : STD_LOGIC := '0';
BEGIN
-- dataflow using MUX2_1 logic
q_int <= D WHEN EN = '1' ELSE q_int;
Q <= q_int;
QB <= NOT q_int;
END impl_3;
VHDL Testbench:
-- File tb_d_latch.vhd
-- Test bench for D latch
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
ENTITY tb_d_latch IS
END tb_d_latch;
ARCHITECTURE sim OF tb_d_latch IS
COMPONENT d_latch
PORT (
D : IN STD_LOGIC;
EN : IN STD_LOGIC;
Q : OUT STD_LOGIC;
QB : OUT STD_LOGIC
);
END COMPONENT;
SIGNAL t_D, t_EN, t_Q, t_QB : STD_LOGIC;
-- LFSR reg
SIGNAL lfsr : STD_LOGIC_VECTOR(7 DOWNTO 0) := (OTHERS => '1');
BEGIN
-- Instantiate D latch
uut : ENTITY work.d_latch(impl_2)
PORT MAP(
D => t_D,
EN => t_EN,
Q => t_Q,
QB => t_QB
);
-- 8-bit LFSR
lfsr_proc : PROCESS
BEGIN
lfsr <= lfsr(6 DOWNTO 0) & (lfsr(7) XOR lfsr(5));
WAIT FOR 10 ns;
END PROCESS;
t_d <= lfsr(0);
t_en <= '0',
'1' AFTER 100 ns,
'0' AFTER 200 ns,
'1' AFTER 300 ns,
'0' AFTER 1000 ns;
END sim;
GHDL commands for simulation:
ghdl -a rs_latch.vhd d_latch.vhd tb_d_latch.vhd
ghdl -e tb_d_latch
ghdl -r tb_d_latch --vcd=./wave.vcd --stop-time=1us
Simulation Results:
Figure: VHDL simulation (for the implementation impl_1
)
Figure: VHDL simulation (for the implementation impl_2
)
As seen in the simulation waveform, when the enable signal (EN
)
is asserted (high), the D latch updates its output (Q
) in response to
changes on the input (D
). When the enable signal is deasserted (low),
the output remains unchanged.
Alternatively, a D latch can be implemented using a 2-to-1 multiplexer
with two data inputs (D0
and D1
) and a select signal (SEL
).
- The enable input (
EN
) of the D latch is connected to the multiplexer'sSEL
input. - The first data input (
D0
) of the multiplexer is connected to the latch's output (Q
), creating a feedback path. - The second data input (
D1
) is connected to the data input (D
) of the D latch.
This design ensures that:
- When EN
= 0, the latch holds its current value (feedback).
- When EN
= 1, the latch updates its output based on the data input.
VHDL Code for MUX-based D-Latch Implementation
-- File: d_latch_mux.vhd
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
ENTITY d_latch_mux IS
PORT (
D : IN STD_LOGIC; -- Data input
EN : IN STD_LOGIC; -- Enable (acts as SEL for MUX)
Q : OUT STD_LOGIC -- Latch output
);
END ENTITY;
ARCHITECTURE Behavioral OF d_latch_mux IS
-- Internal signals for MUX2_1
SIGNAL mux_d0, mux_d1, mux_sel : STD_LOGIC;
SIGNAL mux_o : STD_LOGIC := '0';
BEGIN
mux_d0 <= mux_o;
mux_d1 <= D;
mux_sel <= EN;
mux2_1_proc: PROCESS (mux_d0, mux_d1, mux_sel, mux_o)
BEGIN
-- 2-to-1 MUX logic:
IF mux_sel = '1' THEN
mux_o <= mux_d1;
ELSE
mux_o <= mux_d0; -- Hold value (feedback path)
END IF;
END PROCESS;
Q <= mux_o;
END ARCHITECTURE;
Notes:
- In FPGA-based synchronous designs, edge-triggered D flip-flops* are preferred over D latches due to their predictable timing behavior and better tool support.
- D flip-flops and D latches, when implemented using FPGA resources, may have initial values automatically loaded during FPGA configuration.
- However, as a good coding practice, every synchronous design should include a global reset input to explicitly initialize all internal storage elements to a known state.
- In functional simulation, all flip-flops update their outputs immediately after the active clock edge (either rising or falling) depending on their trigger type.
- In contrast, timing simulation takes signal delays into account, including clock-to-Q delay, setup and hold times of flip-flops, gate delays, and other propagation delays in the circuit.
Shift Registers#
An -bit register is a sequential digital circuit composed of an array of D flip-flops connected in parallel and typically driven by a common clock signal. Each flip-flop stores one bit, forming an -bit wide storage unit.
In addition to clock and data inputs, registers may include control signals such as a load or clock enable input. When this signal is deasserted, the contents of the register remain unchanged and are not updated on the clock edge.
An -bit shift register is a special type of register in which data is shifted one bit position per clock cycle. The direction of shifting can be:
- Left-shift register: shifts bits toward the MSB; new data enters at the LSB (least significant bit).
- Right-shift register: shifts bits toward the LSB; new data enters at the MSB (most significant bit).
The following is a VHDL code example that shows how to construct a parameterized left-shifting register using two different implementation styles:
- Structural style using DFF instantiations
- Behavioral style using a
PROCESS
block
When the asynchronous reset (RST_N
) is asserted,
the register is initialized to INIT_VALUE
.
On each rising clock edge, the new bit (SDI
) is shifted into the
LSB of the register and the output bit is available at SDO
.
-- File: d_ff.vhd
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
-- A VHDL model of a positive-edge triggered D-type flip-flop
-- with asynchronous active-low reset (or clear)
ENTITY d_ff IS
GENERIC (
INIT_LOGIC : STD_LOGIC := '0'
);
PORT (
CLK : IN STD_LOGIC; -- clock input
RST_N : IN STD_LOGIC; -- asynchronous active-low reset (or clear) input
D : IN STD_LOGIC; -- data input
Q : OUT STD_LOGIC -- data output
);
END d_ff;
ARCHITECTURE behavioral OF d_ff IS
SIGNAL reg : STD_LOGIC := INIT_LOGIC;
BEGIN
update_proc : PROCESS (RST_N, CLK)
BEGIN
IF RST_N = '0' THEN
reg <= INIT_LOGIC;
ELSIF rising_edge(CLK) THEN
reg <= D;
END IF;
END PROCESS;
-- concurrent output signal assignment
Q <= reg;
END behavioral;
-- File: shift_reg.vhd
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;
ENTITY shift_reg IS
GENERIC (
BW : POSITIVE := 4; -- Bitwidth of the register
INIT_VALUE : INTEGER := 0
);
PORT (
CLK : IN STD_LOGIC; -- clock input
RST_N : IN STD_LOGIC; -- async. reset
SDI : IN STD_LOGIC; -- shift data input
SDO : OUT STD_LOGIC; -- shift data output
Q : OUT STD_LOGIC_VECTOR(BW - 1 DOWNTO 0) -- register output
);
END shift_reg;
ARCHITECTURE impl_1 OF shift_reg IS
COMPONENT d_ff
GENERIC (
INIT_LOGIC : STD_LOGIC := '0'
);
PORT (
CLK : IN STD_LOGIC;
RST_N : IN STD_LOGIC;
D : IN STD_LOGIC;
Q : OUT STD_LOGIC
);
END COMPONENT;
CONSTANT INIT_BITS : unsigned(BW - 1 DOWNTO 0) := to_unsigned(INIT_VALUE, BW);
SIGNAL reg : unsigned(BW - 1 DOWNTO 0);
SIGNAL chain : unsigned(BW DOWNTO 0);
BEGIN
-- internal chain signals
chain <= reg & SDI;
-- Generate D flip-flops
for_gen : FOR i IN 0 TO BW - 1 GENERATE
dff_inst : d_ff
GENERIC MAP(
INIT_LOGIC => INIT_BITS(i)
)
PORT MAP(
CLK => CLK,
rst_n => RST_N,
D => chain(i),
Q => reg(i)
);
END GENERATE for_gen;
-- Output assignments
SDO <= reg(BW - 1);
Q <= STD_LOGIC_VECTOR(reg);
END impl_1;
ARCHITECTURE impl_2 OF shift_reg IS
SIGNAL reg : unsigned(BW - 1 DOWNTO 0) := to_unsigned(INIT_VALUE, BW);
BEGIN
-- The chain signal acts as a shift input path: SDI enters at LSB.
PROCESS (RST_N, CLK)
BEGIN
IF RST_N = '0' THEN
reg <= to_unsigned(INIT_VALUE, BW);
ELSIF rising_edge(CLK) THEN
reg <= reg(BW - 2 DOWNTO 0) & SDI; -- shift left
END IF;
END PROCESS;
SDO <= reg(BW - 1);
Q <= STD_LOGIC_VECTOR(reg);
END impl_2;
VHDL Testbench:
-- File: tb_shift_reg.vhd
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;
ENTITY tb_shift_reg IS
END tb_shift_reg;
ARCHITECTURE sim OF tb_shift_reg IS
-- Bitwidth of the shift register
CONSTANT BW : POSITIVE := 8;
-- Signals to connect to UUT
SIGNAL t_clk : STD_LOGIC := '0';
SIGNAL t_rst_n : STD_LOGIC := '0';
SIGNAL t_sdi : STD_LOGIC := '0';
SIGNAL t_sdo : STD_LOGIC := '0';
SIGNAL t_q : STD_LOGIC_VECTOR(BW - 1 DOWNTO 0);
-- Clock period constant
CONSTANT CLK_PERIOD : TIME := 20 ns;
BEGIN
-- Circular shift left: MSB fed back into LSB
t_sdi <= t_sdo;
-- Instantiate the Unit Under Test (UUT) using direct entity instantiation
uut : ENTITY work.shift_reg(impl_1)
GENERIC MAP (
BW => BW,
INIT_VALUE => 1
)
PORT MAP (
clk => t_clk,
rst_n => t_rst_n,
sdi => t_sdi,
sdo => t_sdo,
q => t_q
);
-- Clock process
clk_process : PROCESS
BEGIN
t_clk <= '0';
WAIT FOR CLK_PERIOD / 2;
t_clk <= '1';
WAIT FOR CLK_PERIOD / 2;
END PROCESS;
-- Stimulus process
stim_proc : PROCESS
BEGIN
-- Apply reset
t_rst_n <= '0';
WAIT FOR 100 ns;
t_rst_n <= '1';
-- Let the register shift for some clock cycles
FOR i IN 0 TO 2 * BW LOOP
WAIT UNTIL rising_edge(t_clk);
END LOOP;
WAIT FOR 100 ns;
-- End simulation
ASSERT false REPORT "End of simulation." SEVERITY note;
WAIT;
END PROCESS;
END sim;
GHDL Commands for Simulation:
ghdl -a d_ff.vhd shift_reg.vhd tb_shift_reg.vhd
ghdl -e tb_shift_reg
ghdl -r tb_shift_reg --fst=wave.fst --stop-time=10us
GHDL Simulation Result:
Figure: VHDL simulation
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Created: 2025-06-07 | Last Updated: 2025-06-07