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

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • Logic Synthesis Glossary
  • Logic Synthesis Cheatsheet
  • Synthesis Foundations14
  • Logic Optimisation and Mapping20
  • Constraints and Timing15
  • Sequential, FSM and Memory19
    • Flops, Resets and Enables
    • FSM Synthesis
      • Coding Styles
      • State Encoding
      • Unreachable States
      • Output Registering
      • Yosys FSM Passes
    • Memory and Macro Inference
    • Retiming and Pipelining
  • Datapath Synthesis14
  • Area, Power and Test14
  • Verification, ECO and Handoff15
  • Flows and Debugging15
  • Interview and Judgment11
Loading lesson

←FSM Synthesis
  1. Topics
  2. Logic Synthesis
  3. Sequential, FSM and Memory
  4. FSM Synthesis
  5. Yosys FSM Passes
On this page

    Part 4 of 9, lesson 10 of 19Part 4 of 9/Sequential, FSM and Memory/Lesson 10 of 19Intermediate

    Yosys FSM Passes

    How fsm_detect, fsm_extract and fsm_recode handle the receiver, and when they refuse.

    Recognition is pattern-based, so a register the detector declines stays plain flops and every later recode silently does nothing.

    After this lesson you can: run detect, extract, recode and map on the receiver, quote the detector's verdicts, and explain initial values, folded enables, and self-resetting shapes.

    Detect, extract, recode, map

    Yosys handles a finite-state machine in named stages: detection marks the state register without changing the circuit, extraction packs the marked machine into one cell, optimisation trims it with a cleanup pass, recoding reassigns the state codes, and mapping unpacks the cell back into flops and gates. The built-in help for the meta pass, checked in this build with yosys -h fsm, lists exactly this order.

    Every stage is pattern-based, which means every stage can decline. Detection only marks registers it recognises: a plain or asynchronous-reset flop feeding a multiplexer tree, with no initial value and no reset-like behaviour. The three scripts below differ in exactly one feature each, and each one quotes the detector's own log lines alongside the cell counts.

    yosys
    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
    read_verilog <<EOV
    module pkt_rx (input wire clk, input wire start, input wire byte_valid, input wire last_byte, input wire crc_ok,
      output reg ready, output reg accept, output reg err);
      localparam [2:0] IDLE = 3'd0, HDR = 3'd1, PAYLOAD = 3'd2, CRC = 3'd3, DONE = 3'd4;
      reg [2:0] state, next;
      initial state = IDLE;
      always @(posedge clk) state <= next;
      always @(*) begin
        next = state; ready = 1'b0; accept = 1'b0; err = 1'b0;
        case (state)
          IDLE: begin ready = 1'b1; if (start) next = HDR; end
          HDR: begin if (byte_valid) next = PAYLOAD; end
          PAYLOAD: begin if (byte_valid && last_byte) next = CRC; end
          CRC: begin if (crc_ok) next = DONE; else begin next = IDLE; err = !crc_ok; end end
          DONE: begin accept = 1'b1; next = IDLE; end
          default: next = IDLE;
        endcase
      end
    endmodule
    EOV
    hierarchy -top pkt_rx
    proc
    opt_clean
    fsm_detect
    fsm_extract
    tee -o coarse_init.txt stat t:$fsm t:$dff t:$dffe t:$adff t:$sdff
    select -assert-none t:$fsm
    log PASS: initialised register is refused by detection
    yosys
    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
    read_verilog <<EOV
    module pkt_rx (input wire clk, input wire start, input wire byte_valid, input wire last_byte, input wire crc_ok,
      output reg ready, output reg accept, output reg err);
      localparam [2:0] IDLE = 3'd0, HDR = 3'd1, PAYLOAD = 3'd2, CRC = 3'd3, DONE = 3'd4;
      reg [2:0] state, next;
      always @(posedge clk) state <= next;
      always @(*) begin
        next = state; ready = 1'b0; accept = 1'b0; err = 1'b0;
        case (state)
          IDLE: begin ready = 1'b1; if (start) next = HDR; end
          HDR: begin if (byte_valid) next = PAYLOAD; end
          PAYLOAD: begin if (byte_valid && last_byte) next = CRC; end
          CRC: begin if (crc_ok) next = DONE; else begin next = IDLE; err = !crc_ok; end end
          DONE: begin accept = 1'b1; next = IDLE; end
          default: next = IDLE;
        endcase
      end
    endmodule
    EOV
    hierarchy -top pkt_rx
    proc
    opt_clean
    fsm_detect
    fsm_extract
    tee -o coarse_noreset.txt stat t:$fsm t:$dff t:$dffe t:$adff t:$sdff
    select -assert-none t:$fsm
    log PASS: reset-free register is refused as self-resetting
    yosys
    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
    read_verilog <<EOV
    module pkt_rx (input wire clk, input wire rst, input wire start, input wire byte_valid, input wire last_byte, input wire crc_ok,
      output reg ready, output reg accept, output reg err);
      localparam [2:0] IDLE = 3'd0, HDR = 3'd1, PAYLOAD = 3'd2, CRC = 3'd3, DONE = 3'd4;
      reg [2:0] state, next;
      always @(posedge clk) if (rst) state <= IDLE; else state <= next;
      always @(*) begin
        next = state; ready = 1'b0; accept = 1'b0; err = 1'b0;
        case (state)
          IDLE: begin ready = 1'b1; if (start) next = HDR; end
          HDR: begin if (byte_valid) next = PAYLOAD; end
          PAYLOAD: begin if (byte_valid && last_byte) next = CRC; end
          CRC: begin if (crc_ok) next = DONE; else begin next = IDLE; err = !crc_ok; end end
          DONE: begin accept = 1'b1; next = IDLE; end
          default: next = IDLE;
        endcase
      end
    endmodule
    EOV
    hierarchy -top pkt_rx
    proc
    opt_clean
    fsm_detect
    fsm_extract
    tee -o coarse_reset.txt stat t:$fsm t:$dff t:$dffe t:$adff t:$sdff
    select -assert-count 1 t:$fsm
    log PASS: reset register is detected and extracted
    Predict first Three registers, three verdicts. Which one does the detector mark, and what does it print for the other two?

    Only the synchronous-reset register is marked, extracting to one $fsm cell. The initialised register is refused for its initial value on top of the self-resetting reading, and the reset-free register is refused as self-resetting only; the table below quotes both log lines in full.

    Register

    Detector log

    $fsm cells

    State driver

    With initial

    Not marking pkt_rx.state as FSM state register: Register has an initialization value. Circuit seems to be self-resetting.

    0

    $dff x1

    Reset-free

    Not marking pkt_rx.state as FSM state register: Circuit seems to be self-resetting.

    0

    $dff x1

    Sync reset

    marked and extracted

    1

    $dff x1

    Read the first row as the correction this lesson exists to make: the initialised register is refused because it carries an initial value, and only secondarily as self-resetting. An initial value is a simulation construct the detector will not reason about, so the register stays a plain flop no matter how clean the case statement looks. The second row isolates the other reason: remove the initial line but keep the machine reset-free, and the detector still declines, this time only as self-resetting. With no reset line anywhere, the always-taken arcs home read as a reset of the machine's own making.

    The third row is the shape the whole topic uses: add the synchronous reset and the machine is found and extracted into one cell. Note the extraction detail the coarse counts preserve: after packing, the design holds one $fsm cell next to one leftover $dff, which keeps generating the original state code until the optimisation passes remove it. That leftover is why the pass list runs fsm_opt with opt_clean and then fsm_opt again.

    The enable folding trap

    There is a second way to stay invisible. The detector only considers registers driven by a plain $dff or an asynchronous-reset $adff cell. An enabled state register folds its enable mux into a $dffe under a full optimisation, and a $dffe driver is not on the detector's list. The two scripts below run the identical enabled receiver twice: once with a full opt that folds, once with the folding disabled.

    yosys
    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
    read_verilog <<EOV
    module pkt_rx (input wire clk, input wire en, input wire start, input wire byte_valid, input wire last_byte, input wire crc_ok,
      output reg ready, output reg accept, output reg err);
      localparam [2:0] IDLE = 3'd0, HDR = 3'd1, PAYLOAD = 3'd2, CRC = 3'd3, DONE = 3'd4;
      reg [2:0] state, next;
      always @(posedge clk) if (en) state <= next;
      always @(*) begin
        next = state; ready = 1'b0; accept = 1'b0; err = 1'b0;
        case (state)
          IDLE: begin ready = 1'b1; if (start) next = HDR; end
          HDR: begin if (byte_valid) next = PAYLOAD; end
          PAYLOAD: begin if (byte_valid && last_byte) next = CRC; end
          CRC: begin if (crc_ok) next = DONE; else begin next = IDLE; err = !crc_ok; end end
          DONE: begin accept = 1'b1; if (start) next = HDR; else next = IDLE; end
        endcase
      end
    endmodule
    EOV
    hierarchy -top pkt_rx
    proc
    opt
    fsm_detect
    fsm_extract
    tee -o coarse_enfull.txt stat t:$fsm t:$dff t:$dffe t:$adff t:$sdff
    select -assert-count 1 t:$dffe
    select -assert-none t:$fsm
    log PASS: folded enable hides the machine from detection
    yosys
    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
    read_verilog <<EOV
    module pkt_rx (input wire clk, input wire en, input wire start, input wire byte_valid, input wire last_byte, input wire crc_ok,
      output reg ready, output reg accept, output reg err);
      localparam [2:0] IDLE = 3'd0, HDR = 3'd1, PAYLOAD = 3'd2, CRC = 3'd3, DONE = 3'd4;
      reg [2:0] state, next;
      always @(posedge clk) if (en) state <= next;
      always @(*) begin
        next = state; ready = 1'b0; accept = 1'b0; err = 1'b0;
        case (state)
          IDLE: begin ready = 1'b1; if (start) next = HDR; end
          HDR: begin if (byte_valid) next = PAYLOAD; end
          PAYLOAD: begin if (byte_valid && last_byte) next = CRC; end
          CRC: begin if (crc_ok) next = DONE; else begin next = IDLE; err = !crc_ok; end end
          DONE: begin accept = 1'b1; if (start) next = HDR; else next = IDLE; end
        endcase
      end
    endmodule
    EOV
    hierarchy -top pkt_rx
    proc
    opt -nodffe -nosdff
    fsm_detect
    fsm_extract
    tee -o coarse_enclean.txt stat t:$fsm t:$dff t:$dffe t:$adff t:$sdff
    select -assert-count 1 t:$fsm
    log PASS: unfolded enable lets detection mark the machine

    Optimisation

    State driver

    Detector log

    $fsm cells

    Full opt

    $dffe x1

    no detection line

    0

    opt -nodffe -nosdff

    $dff x1

    Found FSM state register pkt_rx.state.

    1

    The full-opt row is the quiet one: the enable folds into one $dffe and the detector prints no line at all, because the driver is not a cell type it considers. Silence means skipped, not clean. The second row keeps the enable as gates around a plain $dff, the detector marks the register, and extraction packs one $fsm cell. That is why the stock flow runs opt -nodffe -nosdff before its own detection step: so enables are not yet folded when the detector looks.

    The full pipeline, pinned

    With detection understood, the script below runs the complete pass list on the synchronous-reset receiver: detect, extract, optimise twice around a cleanup, recode to binary, map, then technology mapping and reporting. The assertions pin every handover: one $fsm cell after extraction, none after mapping, three library flops at the end.

    yosys
    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
    read_verilog <<EOV
    module pkt_rx (input wire clk, input wire rst, input wire start, input wire byte_valid, input wire last_byte, input wire crc_ok,
      output reg ready, output reg accept, output reg err);
      localparam [2:0] IDLE = 3'd0, HDR = 3'd1, PAYLOAD = 3'd2, CRC = 3'd3, DONE = 3'd4;
      reg [2:0] state, next;
      always @(posedge clk) if (rst) state <= IDLE; else state <= next;
      always @(*) begin
        next = state; ready = 1'b0; accept = 1'b0; err = 1'b0;
        case (state)
          IDLE: begin ready = 1'b1; if (start) next = HDR; end
          HDR: begin if (byte_valid) next = PAYLOAD; end
          PAYLOAD: begin if (byte_valid && last_byte) next = CRC; end
          CRC: begin if (crc_ok) next = DONE; else begin next = IDLE; err = !crc_ok; end end
          DONE: begin accept = 1'b1; next = IDLE; end
          default: next = IDLE;
        endcase
      end
    endmodule
    EOV
    hierarchy -top pkt_rx
    proc
    opt_clean
    fsm_detect
    fsm_extract
    fsm_opt
    opt_clean
    fsm_opt
    select -assert-count 1 t:$fsm
    fsm_recode -encoding binary
    fsm_map
    select -assert-none t:$fsm
    tee -o coarse_pipe.txt stat
    techmap
    opt_clean
    dfflibmap -liberty course.lib
    tee -o abc_pipe.txt abc -liberty course.lib -constr typical.constr -D 1000
    tee -o stat_pipe.txt stat -liberty course.lib
    write_json net_pipe.json
    select -assert-count 3 t:DFF
    log PASS: binary pipeline maps the receiver to three flops

    Stage

    Evidence

    After extract

    one $fsm cell, and the coarse stat still shows the leftover state driver beside it

    After recode and map

    no $fsm cells: the machine is flops and gates again, in the chosen code

    After ABC and stat

    18 gates, area 42, delay 506.03 ps, chip area 66, 3 state flops

    The final numbers match the binary run of the state-encoding lesson field for field: 18 gates, area 42, delay 506.03 ps, chip area 66. That agreement is a cross-check, not a coincidence: same receiver, same binary code, same library and load, so the pipeline reproduces the earlier result exactly. The drawing below is the recorded mapped netlist from this run, and the flowchart after it names the stages the assertions pinned.

    The recorded pipeline netlist: 3 state flops in blue, mapped from the recoded binary machine.
    Flowchart with six stages: RTL, detect, extract, optimise, recode, map, and library mapping.
    The pass pipeline the script records: detect marks, extract packs, optimisation trims, recoding assigns codes, mapping unpacks, and the backend maps.

    Break it: override the self-reset check

    The reset-free machine is a legitimate design that the detector declines for a defensible reason. When the machine really does return home along unconditional arcs and the designer wants it recognised anyway, the detector offers an explicit override flag. The script below reruns the refused machine with that flag and nothing else changed.

    yosys
    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
    read_verilog <<EOV
    module pkt_rx (input wire clk, input wire start, input wire byte_valid, input wire last_byte, input wire crc_ok,
      output reg ready, output reg accept, output reg err);
      localparam [2:0] IDLE = 3'd0, HDR = 3'd1, PAYLOAD = 3'd2, CRC = 3'd3, DONE = 3'd4;
      reg [2:0] state, next;
      always @(posedge clk) state <= next;
      always @(*) begin
        next = state; ready = 1'b0; accept = 1'b0; err = 1'b0;
        case (state)
          IDLE: begin ready = 1'b1; if (start) next = HDR; end
          HDR: begin if (byte_valid) next = PAYLOAD; end
          PAYLOAD: begin if (byte_valid && last_byte) next = CRC; end
          CRC: begin if (crc_ok) next = DONE; else begin next = IDLE; err = !crc_ok; end end
          DONE: begin accept = 1'b1; next = IDLE; end
          default: next = IDLE;
        endcase
      end
    endmodule
    EOV
    hierarchy -top pkt_rx
    proc
    opt_clean
    fsm_detect -ignore-self-reset
    fsm_extract
    tee -o coarse_ignore.txt stat t:$fsm t:$dff t:$dffe t:$adff t:$sdff
    select -assert-count 1 t:$fsm
    log PASS: overriding the self-reset check marks the machine

    Variant

    $fsm cells

    State driver

    Reset-free, plain detect

    0

    $dff x1

    Reset-free, override flag

    1

    $dff x1

    The recorded run marks the register and extracts one $fsm cell, so the flag does exactly what it promises. Treat it as an override, not a default: it silences a check that also catches genuinely degenerate machines, so use it for a machine whose home arcs are intentional and keep the plain detection for everything else. The assertion pins the single extracted cell, matching the synchronous-reset run's count through a different door.

    Assuming detection always fires. Recognition is pattern-based, so an unrecognised machine stays as ordinary flops and gates and a later recode has no target. Assert the $fsm count right after extraction, before relying on recoding, and match the asserted cell family to the passes that ran: $fsm and coarse cells before mapping, library cells after.

    Q: the detector refuses your state register. Name the three causes you check, in order, and what each one costs.

    Weak answer: rewrite the case statement until it works. Strong answer: first an initial value on the register, which refuses outright; then a folded enable showing as $dffe, which the detector skips silently, so rerun with opt -nodffe -nosdff; then a self-resetting shape with no reset line, which only the sync reset or the explicit override flag fixes. Here those three verdicts read as the initial-value refusal quoted in the table above, no detection line with one $dffe, and the self-reset refusal quoted beside it. Follow-up: when is the override flag the right answer?

    Check yourself The initialised run reports zero $fsm cells with two reason lines, while the reset-free run reports zero with one. What do the reasons say, and which fix applies to each?

    The initialised register carries both an initial value and the self-resetting reading; only removing the initial value and adding a real reset fixes it, because the detector never marks initialised registers. The reset-free register carries only the self-resetting reading, so either a synchronous reset or the explicit override flag marks it.

    Check yourself The full-opt enable run prints no detection line at all. What does the silence mean, and what does the stock flow do about it?

    Silence means the driver folded into $dffe, which the detector never considers, so there is nothing to report and no $fsm cell follows. The stock flow runs opt -nodffe -nosdff before detecting so enables stay as gates around a plain $dff, exactly the recorded second run.

    • An initial value refuses outright; a folded $dffe is skipped silently; a reset-free machine reads as self-resetting.

    • The stock flow runs opt -nodffe -nosdff before detecting so enables stay unfolded.

    • Assert the $fsm count after extraction, and match the cell family to the stage.

    Rule of thumb: quote the detector's log line and pin the $fsm count before relying on any recode.

    How this was checked

    Every verdict, count, ABC figure, chip area, and the netlist come from seven recorded Yosys 0.70+post runs: detection with initial, without reset, and with sync reset; the enable-folding pair; the full binary pipeline with ABC against course.lib (typical.constr); and the override flag. Pass order follows yosys -h fsm in this build. Absolute areas are teaching units.

    • Interview: run a mock interview on this topic

    Related topics

    • Up: FSM Synthesis

    • Coding styles

    • Equivalence Checking

    Previous

    61 / 139

    Next: Memory and Macro Inference

    Up next

    Up nextMemory and Macro Inference

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Coding Styles
    • State Encoding
    • Unreachable States