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. State Encoding
On this page

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

    State Encoding

    Binary, one-hot and gray codes for the five receiver states, with recorded area, speed and switching numbers.

    Numbering five states in bits trades flops against decode depth and switching, and the tool applies its own default when the author stays silent.

    After this lesson you can: recode the receiver three ways, read state flops, logic area and delay, and total chip area off the recorded reports, and say where one-hot wins and loses.

    Three ways to number five states

    An encoding assigns one bit pattern to each state, and the receiver needs five. Binary packs the states into three bits in counting order. One-hot spends one flop per state, with exactly one asserted at a time. Gray keeps the three-bit width but orders the codes so neighbours on the packet walk differ in a single bit, as the table shows.

    State

    Binary

    One-hot

    Gray

    IDLE

    000

    00001

    000

    HDR

    001

    00010

    001

    PAYLOAD

    010

    00100

    011

    CRC

    011

    01000

    010

    DONE

    100

    10000

    110

    The script below runs the same receiver three times, changing only the code assignment. The one-hot run recodes with fsm_recode -encoding one-hot and the binary run with -encoding binary: the two recodings this build's flag applies to a real machine (check with yosys -h fsm_recode), so the gray run keeps hand-written gray codes and maps without a recode step. Anything left to the auto default is mapped per machine instead — one-hot here, the written codes kept where recoding buys nothing — and each run asserts the flop count its encoding implies alongside its ABC report, liberty stat report, and mapped netlist.

    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
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    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
    module pkt_gray (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'b000, HDR = 3'b001, PAYLOAD = 3'b011, CRC = 3'b010, DONE = 3'b110;
      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
    design -save rtl
    design -load rtl
    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 one-hot
    fsm_map
    techmap
    opt_clean
    dfflibmap -liberty course.lib
    tee -o abc_oh.txt abc -liberty course.lib -constr typical.constr -D 1000
    tee -o stat_oh.txt stat -liberty course.lib
    write_json net_oh.json
    select -assert-count 5 t:DFF
    design -load rtl
    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
    techmap
    opt_clean
    dfflibmap -liberty course.lib
    tee -o abc_bin.txt abc -liberty course.lib -constr typical.constr -D 1000
    tee -o stat_bin.txt stat -liberty course.lib
    write_json net_bin.json
    select -assert-count 3 t:DFF
    design -load rtl
    hierarchy -top pkt_gray
    proc
    opt_clean
    fsm_detect
    fsm_extract
    fsm_opt
    opt_clean
    fsm_opt
    select -assert-count 1 t:$fsm
    fsm_map
    techmap
    opt_clean
    dfflibmap -liberty course.lib
    tee -o abc_gray.txt abc -liberty course.lib -constr typical.constr -D 1000
    tee -o stat_gray.txt stat -liberty course.lib
    write_json net_gray.json
    select -assert-count 3 t:DFF
    log PASS: one-hot binary and gray receivers map with five three and three flops
    Predict first One-hot tests a single bit per transition, so it should be fastest, and it carries the most flops, so it should be biggest. Which encoding wins the delay column, which wins the chip column, and by how much?

    One-hot reaches 291.61 ps against 506.03 ps for binary, so it wins delay. Binary needs chip area 66 against 81 for one-hot, so binary wins area, and gray lands between the two on delay at 426.46 ps and between the other two on chip at 67.

    What the three runs printed

    Encoding

    State flops

    ABC gates

    ABC area

    ABC delay

    Chip area

    One-hot

    5

    20

    41

    291.61 ps

    81

    Binary

    3

    18

    42

    506.03 ps

    66

    Gray

    3

    19

    43

    426.46 ps

    67

    Read the delay column first: one-hot answers in 291.61 ps because every transition test watches a single state bit, while binary must decode up to three bits and lands at 506.03 ps. Now read the chip column the other way: the one-hot register needs two more flops than either three-bit code, and the one-hot chip against 66 for binary shows the machine paying for its speed in flops. Gray is the compromise the table promises: three flops like binary, a delay of 426.46 ps between the other two, and the smallest switching total of the three, for a chip between the other two — because its neighbour-to-neighbour hops keep both the decode and the switching small.

    Recorded chip area: one-hot 81, binary 66, gray 67. One-hot pays for its speed in flops.
    Recorded ABC delay: one-hot 291.61 ps, binary 506.03 ps, gray 426.46 ps. One-hot wins by decoding one bit.

    The recorded one-hot netlist below shows where the area goes: five blue state flops on the left, one per state, feeding the next-state and output decode. Compare it with the three-flop binary netlist in the Yosys FSM passes lesson and the cost is visible before any number is read.

    The recorded one-hot netlist: 5 state flops in blue, one per receiver state, against 3 in the binary run.

    Counting the bit flips

    Delay is only half the trade; the other half is how many state bits move on each hop. A hop that flips two bits charges two flop inputs and briefly presents a wrong code to every decoder watching the register. The program below adds up the Hamming distance over the six arcs that leave a state, checks each total against a hand count, and fails loudly on any mismatch.

    example.py
    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
    import sys
    
    # Packet-receiver arcs that leave a state: (source, dest).
    ARCS = [("IDLE", "HDR"), ("HDR", "PAYLOAD"), ("PAYLOAD", "CRC"),
            ("CRC", "DONE"), ("CRC", "IDLE"), ("DONE", "IDLE")]
    
    BIN = {"IDLE": 0b000, "HDR": 0b001, "PAYLOAD": 0b010, "CRC": 0b011, "DONE": 0b100}
    OH = {"IDLE": 0b00001, "HDR": 0b00010, "PAYLOAD": 0b00100, "CRC": 0b01000, "DONE": 0b10000}
    GRAY = {"IDLE": 0b000, "HDR": 0b001, "PAYLOAD": 0b011, "CRC": 0b010, "DONE": 0b110}
    
    def ham(a, b):
        return bin(a ^ b).count("1")
    
    def total(code):
        return sum(ham(code[s], code[d]) for s, d in ARCS)
    
    bin_total, oh_total, gray_total = total(BIN), total(OH), total(GRAY)
    
    checks = [(bin_total, 10), (oh_total, 12), (gray_total, 7)]
    for i in range(len(checks)):
        got, want = checks[i]
        if got != want:
            print("FAIL: check " + str(i + 1) + " got " + str(got) + " want " + str(want))
            sys.exit(1)
    
    # One-hot flips exactly two bits on every hop between distinct states.
    for s, d in ARCS:
        if ham(OH[s], OH[d]) != 2:
            print("FAIL: one-hot hop " + s + " to " + d + " flips not 2")
            sys.exit(1)
    
    print("PASS: hamming totals binary 10 one-hot 12 gray 7 over six arcs")

    The asserted totals are 10 for binary, 12 for one-hot and 7 for gray, and the one-hot figure deserves a second look because it surprises most readers. Every one-hot hop flips exactly two bits, one flop turning off and the next turning on, so its six arcs cost twelve flips, the most of the three. Binary flips vary from one to three bits per hop and totals ten. Gray flips one bit on every forward hop and pays two only on the DONE to IDLE return, totalling seven. One-hot wins the decode depth at one bit tested per transition and loses the switching count at two bits flipped per hop; gray wins the switching and keeps the flop count, but only on machines that mostly walk between neighbours.

    Power follows the clock pins

    Clock power is the part of the bill no encoding can dodge, because every state flop has a clock pin that toggles each cycle whether the state moves or not. The one-hot register drives five clock pins against three for either three-bit code, and the arithmetic below turns that pin count into watts with illustrative parameters, labelled so; the data switching on top follows the flip totals of the previous section.

    Diagram
    EXAMPLE parameters (illustrative, not a process): C 5 fF per clock pin, V 0.8 V, f 500 MHz, activity 1
    P = activity x C x V^2 x f = 1 x 5e-15 F x 0.64 V^2 x 5e8 Hz
      = 5 x 0.64 x 5 e(-15 + 8) W = 16e-7 W = 1.6 uW per clock pin
    binary and gray:   3 pins x 1.6 uW = 4.8 uW of clock-pin power
    one-hot:           5 pins x 1.6 uW = 8.0 uW of clock-pin power
    clock-pin part only; data-pin switching follows the 10 / 12 / 7 flip totals above

    Gray helps only walks between neighbours. On a machine that jumps between distant states, gray codes switch as many bits as binary while keeping the deeper decode, so it buys nothing. Match the code to the shape of the walk, not to the reputation of the code.

    Break it: let the stock flow choose

    All three runs above chose their encoding explicitly. The script below chooses nothing: it runs the binary-written receiver through the stock synthesis flow, which performs its own detection, extraction and recoding with default settings, then maps and reports. If the default were to keep the written codes, the run would land on three flops and the binary chip area.

    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 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
    synth -top pkt_rx
    dfflibmap -liberty course.lib
    tee -o abc_stock.txt abc -liberty course.lib -constr typical.constr -D 1000
    tee -o stat_stock.txt stat -liberty course.lib
    select -assert-count 5 t:DFF
    log PASS: stock flow recodes the receiver to five state flops

    The recorded stock run lands on 5 state flops with chip area 79, within two units of the explicit one-hot chip. The tool recoded the machine on its own, to the one-hot shape: Yosys recodes to one-hot by default, without looking at any delay target or flop budget. That is the honest meaning of an encoding default: not keeping your codes, but applying the tool's own choice. Pin the encoding you evaluated whenever the choice matters, and assert the flop count so a tool update cannot silently change it.

    Q: for a controller with a given state count, one-hot or binary, and what do you pin so the tool keeps your answer?

    Weak answer: one-hot is always faster, so always pick it. Strong answer: one-hot is fastest here but the chip grows from 66 to 81 area units, while gray keeps three flops, lands between the other two on delay and chip, and wins only the switching count. Yosys recodes to one-hot by default, landing the stock flow at 79; commercial tools may weigh delay target against flop budget instead. So recode explicitly and assert the flop count — 3 here for binary — or the default silently decides for you. Follow-up: which machine shape would make you overrule even an explicit one-hot?

    Check yourself The one-hot run flips twelve bits over the six arcs while gray flips seven, yet one-hot is faster. Which two numbers explain each side?

    Speed follows decode depth: one-hot tests one state bit per transition and reaches 291.61 ps against 506.03 ps. The bigger chip follows the flop count: 5 state flops against 3. The flip totals, twelve against seven, govern switching activity, not area.

    Check yourself The stock-flow run reports 5 flops. What did the tool do, and what should you do if you wanted binary?

    It recoded the written binary machine to the one-hot shape on its own, landing within two units of the explicit one-hot chip. To keep binary, recode explicitly with fsm_recode -encoding binary and assert the three-flop count so the default cannot drift.

    • One-hot is fastest and biggest; binary and gray keep three flops.

    • Every one-hot hop flips two bits, so one-hot wins decode depth and loses switching count.

    • The recode flag takes one-hot or binary and the auto default maps the rest; gray is hand-written RTL here.

    Rule of thumb: pin the encoding you evaluated with an asserted flop count, because the stock flow recodes on its own.

    How this was checked

    Flop counts, ABC figures, chip areas, and the netlist come from one recorded Yosys 0.70+post run (detect through map against course.lib with typical.constr); gray is hand-coded RTL. Hamming totals come from the asserted Python program shown; the stock-flow check is a recorded synth -top run landing on five flops. Absolute areas and delays are teaching units. The clock-pin power uses illustrative EXAMPLE parameters, labelled so.

    • Interview: run a mock interview on this topic

    Related topics

    • Up: FSM Synthesis

    • Next: Unreachable states

    • Area, Power and Timing Trade-offs

    Previous

    58 / 139

    Next: Unreachable States

    Up next

    Up nextUnreachable States

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Output Registering
    • Yosys FSM Passes