Synthesise assign s = a + b; for two 4-bit inputs and Yosys reports 14 cells: one AND, six NAND, two XNOR and five XOR gates. Leave out one else in an always @(*) block and the report gains a latch cell, $_DLATCH_P_, with a warning above it.
Neither result is a surprise once you know the pattern. Synthesis reads each code shape and builds the matching kind of hardware. A handful of shapes covers almost all RTL, so you can write down the cell list before you run the tool.
Read each row left to right: the code, the hardware it describes, then the two names Yosys prints for it. Orange boxes hold state; black symbols do not.
Rows 1 to 3 are combinational. A gate, a 2:1 mux and a 4:1 mux: the output follows the inputs, with no memory.
Row 4 is the same @(*) block as row 2 with the else removed. It stores a value, so it gets an orange box.
Rows 5 to 7 are clocked. Each extra condition in the clocked block becomes an extra pin on the flop: enable or reset.
Row 8 is one cell before mapping and 14 gates after it.
Every row in the picture comes from one module in this file. Each module is synthesised on its own.
module and_gate (input wire a, b, output wire y);
assign y = a & b;
endmodule
module mux2 (input wire a, b, sel, output reg y);
always @(*) begin
if (sel) y = b;
else y = a;
end
endmodule
module mux4 (input wire [1:0] sel, input wire a, b, c, d, output reg y);
always @(*) begin
case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
default: y = d;
endcase
end
endmodule
module oops_latch (input wire en, d, output reg q);
always @(*) begin
if (en) q = d;
end
endmodule
module dff (input wire clk, d, output reg q);
always @(posedge clk) q <= d;
endmodule
module dff_en (input wire clk, en, d, output reg q);
always @(posedge clk) if (en) q <= d;
endmodule
module dff_ar (input wire clk, rst_n, d, output reg q);
always @(posedge clk or negedge rst_n)
if (!rst_n) q <= 1'b0;
else q <= d;
endmodule
module add4 (input wire [3:0] a, b, output wire [3:0] s);
assign s = a + b;
endmoduleTwo commands give two views of the same module. The first stops after proc and opt, so it shows one coarse cell per operator. The second runs the full synth script, which maps everything to single-bit gates and flops and prints stat at the end.
yosys -p "read_verilog shapes.v; hierarchy -top add4; proc; opt; stat"
yosys -p "read_verilog shapes.v; synth -top add4"read_verilog parses the file. Nothing is hardware yet.
hierarchy -top picks the module to build and drops the others.
proc turns each always block into cells: muxes for its if and case, a flop for each variable assigned at a clock edge, a latch for each variable that must hold a value without one. Latch warnings come from this step.
opt removes constant and duplicate logic. stat prints the cell table.
synth runs proc, opt and many more passes, then maps every coarse cell to single-bit gates and flops.
Code shape | After proc | After synth | What it is |
|---|---|---|---|
|
|
| An AND gate |
|
|
| A 2:1 multiplexer |
|
| 3 | A 4:1 multiplexer built as a tree of 2:1 muxes |
|
|
| A latch, open while |
|
|
| A D flip-flop on the rising edge |
|
|
| A flip-flop with an active-high enable |
|
|
| A flip-flop with an active-low asynchronous reset to 0 |
|
| 1 | A 4-bit adder made of gates |
The letters after the cell type describe the pins in order. In $_DFF_PN0_, P is the clock (rising edge), N is the reset (active low) and 0 is the value the reset loads. In $_DFFE_PP_, the second P is an enable that is active high. $_DLATCH_P_ is a latch that is transparent while its enable is high.
The event list decides the reset type. Take rst_n out of it and the same if (!rst_n) becomes a synchronous reset:
module dff_sr (input wire clk, rst_n, d, output reg q);
always @(posedge clk)
if (!rst_n) q <= 1'b0;
else q <= d;
endmoduleYosys now reports $sdff before mapping and $_SDFF_PN0_ after it: S for synchronous, then the same clock, reset and value letters. The code changed by one event, and the cell changed kind. A target library without a synchronous-reset flop gets a plain flop with the reset as logic in front of D instead, which is still the same behaviour.
At the coarse level the case is a $pmux, a mux with one select line per case item. Two $eq cells and one $logic_not decode sel into those lines. Mapping sees that the four items cover every value of sel and rebuilds the logic as three 2:1 muxes:
after proc; opt; stat:
=== mux4 ===
...
4 cells
2 $eq
1 $logic_not
1 $pmux
after synth:
=== mux4 ===
...
3 cells
3 $_MUX_Before mapping, + is a single $add cell whatever its width. After mapping it is the gate-level adder the target needs. A wider add means more gates, not more $add cells.
after proc; opt; stat:
=== add4 ===
...
1 cells
1 $add
after synth:
=== add4 ===
...
14 cells
1 $_AND_
6 $_NAND_
2 $_XNOR_
5 $_XOR_The coarse view counts one cell per operator or register, whatever the width. The mapped view counts one cell per gate or per bit of storage. When you predict a cell list, say which view you mean.
Assign a default first and every path assigns q. The latch becomes a single $_AND_ gate, and Yosys reports that no latch was inferred:
module no_latch (input wire en, d, output reg q);
always @(*) begin
q = 1'b0; // default first: every path assigns q
if (en) q = d;
end
endmoduleNo latch inferred for signal `\no_latch.\q' from process `\no_latch.$proc$fix.v:2$1'.Predict before you synthesise. List the operators: one gate, mux or adder for each. List the variables assigned in clocked blocks: one flop per bit, with an enable or reset pin if the code has one. Expect zero latches. Then run the tool and compare. Any cell you did not predict is a question about your code.
Try the habit on one more module before reading the answer.
module quiz (
input wire clk, rst_n, en, sel,
input wire [1:0] a, b,
output reg [1:0] q
);
always @(posedge clk or negedge rst_n)
if (!rst_n) q <= 2'b00;
else if (en) q <= sel ? a : b;
endmoduleOne mux for ?: and one clocked register with an enable and an asynchronous reset. Coarse, each is one cell, because a 2-bit register is still one cell. Mapped, each bit gets its own mux and its own flop:
after proc; opt; stat:
2 cells
1 $adffe
1 $mux
after synth:
4 cells
2 $_DFFE_PN0P_
2 $_MUX_$_DFFE_PN0P_ reads as rising clock, active-low reset to 0, active-high enable. No latch appears, because the only process is clocked.
$_DLATCH_P_ in a design that has no latch in its specification. Where do you look first?For an always @(*) (or always_comb) block in which some output is not assigned on every path, usually an if with no else or a case with no default. The "Latch inferred" warning names the signal and the line of the process.
Tool: Yosys 0.69+post (git sha1 143eb14f9), run on 2026-10-17. Commands, for each module of shapes.v: yosys -p "read_verilog shapes.v; hierarchy -top <module>; proc; opt; stat" and yosys -p "read_verilog shapes.v; synth -top <module>". The same two commands were run on quiz.v, on sync.v (module dff_sr), and the synth command on fix.v (module no_latch).
Result: every cell count, cell name and log line on the page is copied from those runs, trimmed with ... marks. and_gate $and to $_AND_; mux2 $mux to $_MUX_; mux4 2 $eq, 1 $logic_not, 1 $pmux to 3 $_MUX_; oops_latch $dlatch to $_DLATCH_P_ with the Latch inferred warning at shapes.v line 24; dff $dff to $_DFF_P_; dff_en $dffe to $_DFFE_PP_; dff_ar $adff to $_DFF_PN0_; dff_sr $sdff to $_SDFF_PN0_; add4 $add to 14 gates; no_latch maps to one $_AND_; quiz $adffe and $mux to 2 $_DFFE_PN0P_ and 2 $_MUX_.
Scope and limits: Yosys generic cells only, no technology library, so the counts say what kind of hardware is built, not its area or delay. Another tool or target may map the same shapes to different cells (for example a library flop with built-in enable or a mux made of AND-OR gates).