Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
    • Digital Logic for RTL Engineers
    • Number Systems for RTL Engineers
    • Verilog Fundamentals
      • Modules and Ports
      • Wire vs reg
      • assign vs always
      • The Two Process Templates
      • Blocking vs Non-blocking
      • From Code to Hardware
    • Verilog Operators and Types
    • SystemVerilog for RTL
    • Simulation and Testbenches
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Verilog Fundamentals
  1. Topics
  2. RTL Design
  3. RTL Foundations
  4. Verilog Fundamentals
  5. From Code to Hardware

Part 1 · Foundations · Beginner

From Code to Hardware

Each code shape becomes one kind of cell: a gate, a mux, a latch, a flip-flop or an adder, and you can predict which before running synthesis.

On this page

    Why this matters

    Leave one else out of an always @(*) block and synthesis builds a latch; write a 4-bit + and it builds 14 gates. Each code shape maps to one kind of cell, so the netlist can be predicted before the tool runs.

    After this lesson you can: predict the cells Yosys reports for a gate, a mux, a case, a latch, a plain, enabled or reset flop and an adder, and spot an accidental latch in a stat list.

    Each code shape becomes one kind of cell

    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.

    Eight code shapes from shapes.v, the hardware each describes, and the cell Yosys reports after proc and after synth

    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.

    Eight shapes in one file

    Every row in the picture comes from one module in this file. Each module is synthesised on its own.

    and_gate.v
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    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;
    endmodule

    Two 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.

    Diagram
    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

    assign y = a & b;

    $and

    $_AND_

    An AND gate

    if/else in always @(*)

    $mux

    $_MUX_

    A 2:1 multiplexer

    case with four items

    $pmux, 2 $eq, 1 $logic_not

    3 $_MUX_

    A 4:1 multiplexer built as a tree of 2:1 muxes

    if without else in always @(*)

    $dlatch

    $_DLATCH_P_

    A latch, open while en is 1

    q <= d in always @(posedge clk)

    $dff

    $_DFF_P_

    A D flip-flop on the rising edge

    if (en) q <= d in a clocked block

    $dffe

    $_DFFE_PP_

    A flip-flop with an active-high enable

    negedge rst_n in the event list

    $adff

    $_DFF_PN0_

    A flip-flop with an active-low asynchronous reset to 0

    assign s = a + b; (4 bits)

    $add

    1 $_AND_, 6 $_NAND_, 2 $_XNOR_, 5 $_XOR_

    A 4-bit adder made of gates

    Reading the mapped cell names

    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:

    dff_sr.v
    1
    2
    3
    4
    5
    module dff_sr (input wire clk, rst_n, d, output reg q);
      always @(posedge clk)
        if (!rst_n) q <= 1'b0;
        else        q <= d;
    endmodule

    Yosys 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.

    The case becomes a mux tree

    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:

    Diagram
    after proc; opt; stat:
    === mux4 ===
    ...
            4 cells
            2   $eq
            1   $logic_not
            1   $pmux
    
    after synth:
    === mux4 ===
    ...
            3 cells
            3   $_MUX_

    The adder is one cell, then gates

    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.

    Diagram
    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.

    Common mistakes

    Common pitfall

    The accidental latch. oops_latch looks like a mux with one input missing, but when en is 0 nothing assigns q, so q must keep its old value. Keeping a value without a clock is a latch. Yosys says so while it converts the process, before any mapping, and the line number points at the always @(*) of oops_latch:

    Diagram
    Warning: Latch inferred for signal `\oops_latch.\q' from process `\oops_latch.$proc$shapes.v:24$4': $auto$proc_dlatch.cc:547:proc_dlatch$23

    Assign a default first and every path assigns q. The latch becomes a single $_AND_ gate, and Yosys reports that no latch was inferred:

    no_latch.v
    1
    2
    3
    4
    5
    6
    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
    endmodule
    Diagram
    No 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.

    quiz.v
    1
    2
    3
    4
    5
    6
    7
    8
    9
    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;
    endmodule
    Check yourself Predict the cells Yosys reports for quiz above, first after proc; opt; stat, then after synth.

    One 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:

    Diagram
    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.

    Check yourself A stat list shows $_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.

    Remember

    • Operators become gates, muxes or adders. Variables assigned in a clocked block become one flop per bit, with enable and reset pins taken from the code.

    • An always @(*) output that is not assigned on every path becomes a latch, and Yosys warns while it converts the process.

    • Before mapping, stat counts one cell per operator or register. After synth it counts one cell per gate or per bit.

    Rule of thumb: write down the cell list before you synthesise; any cell you did not predict is a question about your code.

    How this was checked

    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).

    Keep going

    • Practice: Level-sensitive latch: when it is risky

    • Code lab: Build a 4-bit 4:1 mux

    • Code lab: Build a D flip-flop with asynchronous reset

    • Interview: run a mock interview on this topic

    Related topics

    • Wire vs reg

    • Latch inference

    • Reading a synthesis report

    Previous

    18 / 350

    Next: Verilog Operators and Types

    Up next

    Up nextVerilog Operators and Types

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Modules and Ports
    • Wire vs reg
    • assign vs always