D flip-flop
A D flip-flop takes d at each rising edge of the clock and holds it until the next. The always_ff that describes it, an enable, and an asynchronous reset.
- Level
- rtl
- Code
- d-flip-flop
- Checked
- Verilator 5.052, Icarus Verilog 13.0, Yosys 0.66
- Published
- Authorship
- Drafted with AI assistance
- Status
- In progress — published so it can be reviewed, not finished.
A D flip-flop is one bit of memory with a clock. At each rising edge of clk it takes the value
on d and holds it on q until the next edge, whatever d does in between. Every register in
a synchronous design is a row of these, and always_ff is how SystemVerilog describes one.
Run plays this scenario from the start; driving an input continues from here.
A circuit that runs: a schematic and its waveform on one time axis. Inputs: d, en, rst_n. Outputs: q. clk runs at 8 ticks a period. It is built from 1 multiplexer, 1 D flip-flop. The waveform shows clk, rst_n, en, d, q.
Figure 1. Press Run. While rst_n is high, q changes only at a rising edge: it follows d at the first two, ignores the pulse between the second and the third, and holds at the fourth with en low.
How it works, edge by edge
Follow the q row. The rising edges of clk are at ticks 4, 12, 20 and 28, and q changes at
edges and nowhere else. It has a value even before the first: rst_n is low from tick 0, and an
asynchronous reset gives q its 0 at once, without waiting for an edge. The reset is released at
tick 2, between edges, and the release by itself changes nothing.
At the first edge q becomes 1: d has been 1 since tick 1. At the second edge q becomes 0,
because d fell at tick 7. In both, the value q takes is the one d held just before the edge.
Between the second and the third edge d rises at tick 14 and falls again at 17. No edge falls
inside that pulse, so at tick 20 d is 0 again and q stays 0. A flip-flop samples; it does
not follow.
d rises at tick 21, and en falls at 22. With en at 1 the multiplexer in front of the flop
passes d; at 0 it passes q back into the flop's own input. So at tick 28 the edge arrives as
usual and loads what q already holds: q stays 0 although d is 1. The clock never stopped.
On the edge drives d the way the output of another flip-flop on the same clock changes in a
simulation without delays: in the tick of each rising edge. d rises at tick 4 and q does not,
because the edge takes the 0 that d held just before it. q rises at 12 instead, one period
later, and from then on the q row is the d row shifted right by a clock period.
Reset pulls rst_n low at tick 9, in the middle of a period. q falls in the same tick,
with no edge. The edge at tick 12 changes nothing, because the reset holds q at 0 whatever d
is. rst_n rises again at 17, between edges, and q stays 0 until the edge at 20 loads the 1
on d.
The code
// A D flip-flop with an enable: at each rising edge of clk it takes d if en is
// high and keeps what it has if not. Between edges nothing on d reaches q.
//
// Reset style for this module: asynchronous, active low. One style per page,
// stated up front, so nothing on the page has to be read twice.
module d_flip_flop (
input logic clk,
input logic rst_n,
input logic en,
input logic d,
output logic q
);
// rst_n is in the sensitivity list, so the reset acts the moment it falls;
// its release is seen only by the next rising edge of clk.
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
q <= 1'b0;
end else if (en) begin
// No else: with en low the flop keeps q, which is what a flop does.
q <= d;
end
end
endmoduleThe reset style is stated at the top of the file and used once: asynchronous, active low. The
always_ff block is the flip-flop, and rst_n in its sensitivity list is what makes the reset
act the moment it falls rather than at the next edge.
The multiplexer in the drawing is the else if (en). When en is 0 no branch assigns q, and
an always_ff that assigns nothing at an edge keeps q — which is exactly the multiplexer's 0
input feeding q back. There is no else q <= q; because none is needed. Synthesis folds the
multiplexer back into the flip-flop: the result below is a flop with its enable built in.
Synthesis result
Yosys's generic synthesis turns d_flip_flop.sv into 1 cell, 1 of them a flip-flop. The cells are Yosys's own gate library, not an FPGA's or a standard-cell library's: the count measures the logic, not the area of any chip.
| Cell | What it is | Count |
|---|---|---|
$_DFFE_PN0P_ | D flip-flop on the rising edge, active-low asynchronous reset to 0, active-high enable | 1 |
| Total | 1 | |
Lint by Verilator 5.052, the testbench by Icarus Verilog 13.0, synthesis by Yosys 0.66.
Icarus replayed this page's three scenarios on this code, and all 510 values the waveform draws match.
The netlist before synthesis
What Yosys's prep reads in d_flip_flop.sv, before any gate is chosen, drawn by netlistsvg 1.0.2.
Pitfalls
A blocking assignment in always_ff. Write q = d; instead of q <= d; and this one flop
does not change: Yosys builds the same netlist from both. What breaks is the simulation of
everything else that reads q at that edge. With =, q takes its new value in the same step,
and another always_ff reading q sees the new value or the old one depending on which block the
simulator happens to run first. On the edge shows what every reader of a flop must see: the
value from before the edge, which is what <= guarantees. Verilator's -Wall and Verible both
flag = in always_ff. On a chain of flops the order of the lines then decides what is built,
as the shift register shows.
An enable written as an incomplete if, in the wrong block. Here if (en) with no else is
the enable, because always_ff keeps q when nothing assigns it. Move the choice into
combinational logic — if (en) d_next = d; in an always_comb feeding the flop — and when en
is 0 nothing keeps d_next but a latch: a store that is open while en is high, sitting in
front of the flop. Verilator's -Wall warns of it, and Yosys refuses to synthesize an
always_comb that needs one; Verible, which lints this site's snippets, says nothing. Written as
always @*, the same lines build the latch.
A reset released near an edge. Reset releases rst_n between edges, and the edge after
it loads d. Where a release comes from decides whether that is safe. A release launched by a
flop on the same clock changes just after an edge, like any flop's output, and timing analysis
checks it against the edges on either side of it, as it checks the setup and hold of any other
path.
A release from a pin or from another clock domain can land on the edge itself. In simulation the
answer then depends on how the release is driven. Written as a flop's own output with <=, the
release holds the flop in reset at that edge. Written with =, it races the edge: whichever block
the simulator runs first wins, and two simulators can disagree on the same code. In silicon a
release that close to an edge breaks the flop's recovery or removal time and can leave it
metastable, which this engine does not check. Keeping the release away from the edge is what a
reset synchronizer is for.
Reading the waveform one edge early. In On the edge, d and the edge change in the same
tick, and it is tempting to read q as changing with them. It does not: the edge takes what d
held before it, and q shows that until the next edge. Read a flop's output after its edge, and
its input just before.
Check yourself
- In Sample and hold, d goes to 1 at tick 14 and back to 0 at tick 17. Why does q never show it?
- Because no rising edge falls inside that pulse. The flop looks at d only at an edge, and takes the value d had just before it; at the edge at tick 20, d is 0 again. A pulse between two edges does not exist as far as a flip-flop is concerned, so a signal that has to be seen must still be there at an edge.
- The code has no else for en. Why is that not a latch?
- Because the block runs only at a clock edge or a reset, as always_ff says. When nothing assigns q at an edge, the flop keeps what it has, and keeping is what a flop is for. The multiplexer's 0 input in the drawing is that missing else, q fed back into the flop's D input. The same if with no else in an always_comb has nowhere to keep the value but a latch.
- In Reset, rst_n is released at tick 17, between two edges. What if it were released exactly on the rising edge at tick 20?
- If the release came from a flop on the same clock, the edge would still see rst_n low, and q would load d one edge later, at tick 28, which is the rule this engine follows for a release on an edge. In silicon a release that close to an edge breaks the flop's recovery or removal time and can leave it metastable, and this engine does not model that. Keeping the release away from the edge is the job of a reset synchronizer.
Challenge: Stretch the pulse
Make q match the target: 1 at the edges numbered 1 and 2, then 0 at the edge numbered 3. Only en is yours.
- Your input
-
- en, starting at 1
- The script
-
- d starts at 0.
- rst_n starts at 0.
- rst_n becomes 1 before the edge numbered 0.
- d becomes 1 at the edge numbered 0.
- d becomes 0 at the edge numbered 1.
- The goal
-
- q is 1 at the edge numbered 1.
- q is 1 at the edge numbered 2.
- q is 0 at the edge numbered 3.
The target beside the inputs the script drives. A dash leaves a cycle open: no edge in it is judged.
With JavaScript on, Start plays this challenge in the figure.