The idea: code that describes hardware

A hardware description language (HDL) such as Verilog or SystemVerilog looks like a programming language, but it does not run line after line. It describes a circuit. A synthesizer (Vivado, Quartus, Yosys, Design Compiler) reads the code and turns each construct into gates, multiplexers and registers. Then all of that hardware works at the same time.

So the useful skill is to read a line of HDL and see the hardware it will make. The page shows a short snippet on the left and the circuit on the right. Synthesize lights up one line at a time, flies its code over, and draws the hardware it becomes in the same orange. Simulate then applies a row of inputs (or one clock edge) and colours each wire: green for 1, grey for 0.

Three lines of SystemVerilog with arrows to what they synthesize to: assign y = a & b | c to an AND gate feeding an OR gate, assign y = s ? d1 : d0 to a 2:1 multiplexer, and always_ff q <= d to a D flip-flop
Each construct has a hardware shape: operators become gates, ?: becomes a mux, and always_ff becomes flip-flops.

assign: gates and muxes

An assign statement is continuous: the left side always equals the right side. That is exactly what combinational logic does, so the synthesizer builds gates. In assign y = a & b | c; the & binds tighter than |, so it is (a & b) | c: an AND gate feeding an OR gate. One line, two gates. (Demo: an assign becomes gates.)

The conditional operator s ? d1 : d0 picks one of two values, which is a 2:1 multiplexer with s on its select input. With 4-bit d0 and d1 the tool simply builds four of them, one per bit. (Demo: ?: becomes a mux.)

CodeHardware
&, |, ^, ~AND, OR, XOR gates, inverters
s ? d1 : d02:1 multiplexer
a + b, a == ban adder, a comparator (built from gates)
always_comb casea decoder or a wide mux
always_ff @(posedge clk)flip-flops

always_ff: flip-flops, reset and enable

always_ff @(posedge clk) q <= d; says: at every rising edge of clk, copy d into q, and hold it until the next edge. That is a D flip-flop. Every signal assigned inside an always_ff block becomes a flip-flop (or a bank of them for a bus).

Two common variants change the hardware a little:

  • Asynchronous reset: @(posedge clk, posedge reset) with if (reset) q <= 0; first. The reset goes to the flip-flop's own reset pin, so it clears q at once, without waiting for the clock. A synchronous reset (only posedge clk in the list) would instead be an AND gate in front of D.
  • Enable: else if (en) q <= d;. When en is 0 nothing is assigned, and in always_ff that simply means "keep the value". The hardware is a mux in front of D that feeds q back, so the flip-flop reloads its own value.

In Demo: flip-flop with reset and enable, row 1 has reset = 1 and q is 0 before the edge; row 2 has en = 0 and q stays 0 although d = 1; row 3 loads 1; row 4 keeps 1 although d = 0; row 5 loads 0.

always_comb and case: a decoder

always_comb marks a block of combinational logic: its outputs are recomputed whenever an input changes. A case that sets one output bit for each value of a is a decoder. Each case item becomes one AND gate that is 1 for exactly one value of a (a minterm); a bubble on an AND input means that bit must be 0. (Demo: case becomes a decoder.) A case that picks one of several data inputs would become a wide multiplexer instead.

The latch you did not mean to create

In combinational logic every output must have a value for every input. Look at this block:

always_comb
  if (en) q = d;      // and when en = 0?

When en is 0 no line assigns q. The language says q then keeps its old value. Gates cannot remember anything, so the synthesizer adds a latch, a storage element that is open (transparent) while en = 1 and holds while en = 0, and prints a warning such as "latch inferred for q". The code looked like logic; the hardware has memory.

In the demo, row 3 shows the bug: en = 0 and d = 0, but q is still 1. Adding else q = 1'b0;, or a default assignment q = 1'b0; before the if, gives q a value on every path, and the same idea becomes one AND gate: q = en & d. (Demo: the latch you did not mean, and the fix.)

Left: if (en) q = d with no else synthesizes to a latch, marked with a warning, and q stays 1 when en = 0 and d = 0. Right: with else q = 0 it synthesizes to an AND gate and q is 0
A missing else means "remember": the tool adds a latch. Assign q on every path and it is plain logic again.

Latches are not wrong in themselves, but an unplanned one breaks timing analysis and makes the circuit depend on how long en is high. always_comb helps: many tools warn or fail when an always_comb block infers a latch. If you really want one, write always_latch.

Blocking (=) vs nonblocking (<=)

Inside an always block there are two kinds of assignment:

  • Nonblocking <=: all right sides are read first, with the values from before the edge, and all left sides change together at the end. This matches flip-flops, which all sample at the same clock edge.
  • Blocking =: each line takes effect immediately, so the next line already sees the new value, like a program.

Take a three-stage shift register. With <=, q2 <= q1 reads the old q1, so each value moves one stage per edge: three flip-flops in a chain. With =, q1 = d runs first, then q2 = q1 reads the new q1, which is already d, and q3 too. All three equal d, so the tool builds one flip-flop and wires all three outputs to it. (Demo: <= vs = in a shift register.)

Top: with nonblocking assignments, three flip-flops in a chain; q1 q2 q3 after four edges with d = 1, 0, 1, 1 are 100, 010, 101, 110. Bottom: with blocking assignments, one flip-flop drives q1, q2 and q3; they are 111, 000, 111, 111
Same three lines: <= gives a shift register, = gives one flip-flop copied three times.
Edgedwith <= (q1 q2 q3)with = (q1 q2 q3)
111 0 01 1 1
200 1 00 0 0
311 0 11 1 1
411 1 01 1 1

Rules of thumb

  • Clocked logic: always_ff @(posedge clk) and only <=.
  • Combinational logic: assign, or always_comb with =, and give every output a value on every path (an else, a default, or a default assignment at the top).
  • Assign each signal in only one always block or assign.
  • Read the synthesis report: count the flip-flops and look for "latch" warnings. If the numbers surprise you, the code does not say what you meant.

See also Combinational Logic for these gates and muxes switching with real delays, Sequential Logic for how a latch and a flip-flop are built from gates, Finite State Machines for the state register plus next-state logic that an HDL FSM becomes, and Memory Arrays for the lookup tables an FPGA uses to hold this logic.

What the page leaves out

Real synthesis optimizes the logic (Boolean minimization, sharing, retiming) and maps it to a cell library or to FPGA lookup tables and flip-flops, so the netlist rarely looks as tidy as these drawings. Buses, parameters, generate loops, arithmetic operators, synchronous reset, simulation-only code (initial, delays like #5), X values and timing constraints are left out. The simulator here applies inputs one row at a time with no gate delays.