Combinational logic forgets its inputs instantly. To remember, hardware
needs feedback, and it comes in two flavors:
A latch is level-sensitive: while its enable is high it is
transparent (output follows input); when enable drops it holds the last
value. Data can race through an open latch, which makes timing analysis
much harder to pin down.
A flip-flop samples its input only on the clock edge and holds it
the rest of the cycle. This single discipline, everything changes at
one instant, is what makes synchronous design analyzable, and it's why
essentially all FPGA design is flip-flop based.
The design contains one of each; the waveform makes the difference vivid.
While le (latch enable) is high, q_latch wiggles whenever d wiggles.
q_ff changes only at the rising clock edges, no matter what d does
in between.
The classic accident. In Verilog, an always @* block whose output
isn't assigned on every path infers a latch, the tool has to remember
the old value somehow. If you ever see the synthesis warning
inferring latch for variable 'y', you forgot an else or a default.
It's rarely what you wanted. The fix is mechanical: assign a default at
the top of the block, or complete the branches.
Experiment: in the playground, delete the else from the latch
process, the code still simulates identically. That's what makes this
bug sneaky: simulation looks fine, and the surprise arrives in synthesis.
Schematic
The design
Verilog, design.v
// A D-latch (level-sensitive) and a D flip-flop (edge-sensitive).
module storage (
input wire clk,
input wire le, // latch enable
input wire d,
output reg q_latch,
output reg q_ff
);
// Latch: no edge in the sensitivity - "while le, follow d".
// (This is a DELIBERATE latch. Accidental ones look the same.)
always @* begin
if (le)
q_latch = d;
// no else: when le is low, q_latch holds -> latch inferred
end
// Flip-flop: the @(posedge clk) edge is what makes it a register.
always @(posedge clk)
q_ff <= d;
endmodule
Show the VHDL version
VHDL, design.vhd
-- A D-latch and a D flip-flop.
library ieee;
use ieee.std_logic_1164.all;
entity storage is
port (
clk : in std_logic;
le : in std_logic;
d : in std_logic;
q_latch : out std_logic;
q_ff : out std_logic
);
end entity;
architecture rtl of storage is
begin
-- Latch: assignment guarded by a level, no edge.
process (all) begin
if le = '1' then
q_latch <= d;
end if; -- no else: holds -> latch
end process;
-- Flip-flop: rising_edge() makes it a register.
process (clk) begin
if rising_edge(clk) then
q_ff <= d;
end if;
end process;
end architecture;
Show the MyHDL (Python) version
MyHDL, design.py
from myhdl import block, Signal, always, always_comb, instance, delay, StopSimulation
@block
def storage(clk, le, d, q_latch, q_ff):
@always(le, d) # level-sensitive -> a latch (on purpose)
def latch():
if le:
q_latch.next = d
@always(clk.posedge) # the edge is what makes it a register
def ff():
q_ff.next = d
return latch, ff
@block
def tb():
clk, le, d = [Signal(bool(0)) for _ in range(3)]
q_latch, q_ff = Signal(bool(0)), Signal(bool(0))
dut = storage(clk, le, d, q_latch, q_ff)
@always(delay(10))
def clkgen():
clk.next = not clk
@instance
def stim():
yield delay(4); d.next = 1
yield delay(8); le.next = 1
yield delay(5); d.next = 0
yield delay(5); d.next = 1
yield delay(6); le.next = 0
yield delay(6); d.next = 0
yield delay(16); raise StopSimulation
return dut, clkgen, stim
inst = tb()
inst.config_sim(trace=True)
inst.run_sim()
The testbench
Verilog, tb.v
`timescale 1ns/1ns
module tb;
reg clk = 0, le = 0, d = 0;
wire q_latch, q_ff;
storage dut (.clk(clk), .le(le), .d(d), .q_latch(q_latch), .q_ff(q_ff));
always #10 clk = ~clk; // slow clock so the difference is visible
initial begin
$dumpfile("wave.vcd"); $dumpvars(0, tb);
#4 d = 1; // FF ignores this until the next edge
#8 le = 1; // latch opens: q_latch follows d...
#5 d = 0;
#5 d = 1; // ...wiggle for wiggle
#6 le = 0; // latch closes, holds last value
#6 d = 0;
#16 $finish;
end
endmodule
Show the VHDL testbench
VHDL, tb.vhd
library ieee;
use ieee.std_logic_1164.all;
entity tb is end entity;
architecture sim of tb is
signal clk, le, d : std_logic := '0';
signal q_latch, q_ff : std_logic;
begin
dut : entity work.storage port map (clk=>clk, le=>le, d=>d,
q_latch=>q_latch, q_ff=>q_ff);
clk <= not clk after 10 ns; -- slow clock so the difference shows
process begin
wait for 4 ns; d <= '1'; -- FF ignores until next edge
wait for 8 ns; le <= '1'; -- latch opens, follows d
wait for 5 ns; d <= '0';
wait for 5 ns; d <= '1';
wait for 6 ns; le <= '0'; -- latch closes, holds
wait for 6 ns; d <= '0';
wait for 16 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.