← RTL Design Interviews

Interview Question · Concept

Choosing a State Encoding

L1 JuniorL2 Mid-LevelL3 Seniorfsmstate encodingsynthesis

The Question

The interviewer asks

Binary, one-hot or Gray: how do you choose a state encoding, and how do you check the tool used it?

Reveal the Model Answer

Binary uses the fewest flops, about log2 of the state count, but needs decoding. One-hot uses one flop per state, so testing a state reads one bit. Gray changes one bit per step, which suits machines that move in a fixed sequence. The real answer comes from measuring, because it depends on the machine and the tool. Check the flop count in the report: synthesis tools may re-encode your FSM.

The Overlapping 1011 DetectorEach arrow is a transition at a clock edge, labelled with the bit sampled there. Two self-loops are listed in the node text: S0 stays on 0 and S1 stays on 1.
The overlapping 1011 detectorEach arrow is a transition at a clock edge, labelled with the bit sampled there. Two self-loops are listed in the node text: S0 stays on 0 and S1 stays on 1. component S0 seen nothing; stayson 0 component S1 seen "1"; stays on 1 component S10 seen "10" component S101 seen "101"; a 1 nowcompletes 1011 10101: match0
  1. Climb the pattern

    Each expected bit moves the machine one state to the right: 1, then 0, then 1 reaches S101.

    Follow the calls, values and waits
    1. data sampled bit: S0 → S1
    2. data sampled bit: S1 → S10
    3. data sampled bit: S10 → S101
  2. A match keeps its last 1

    From S101 a 1 completes 1011 and raises match. The stream now ends in "11", and its longest tail that starts 1011 is "1", so the next state is S1.

    Follow the calls, values and waits
    1. data sampled bit: S101 → S1
  3. A miss keeps what it can

    From S101 a 0 makes "1010". Its tail "10" still starts 1011, so the next state is S10. From S10 a 0 makes "100", and no tail starts 1011: back to S0.

    Follow the calls, values and waits
    1. data sampled bit: S101 → S10
Read the diagram as text
  • S0: seen nothing; stays on 0
  • S1: seen "1"; stays on 1
  • S10: seen "10"
  • S101: seen "101"; a 1 now completes 1011
  • S0 → S1: 1
  • S1 → S10: 0
  • S10 → S101: 1
  • S10 → S0: 0
  • S101 → S1: 1: match
  • S101 → S10: 0

From lesson R3.4: a machine with four states. Binary codes it in 2 flops; one-hot uses 4, one per state.

One five-state sequencer, three encodings
EncodingFlopsCellsLevels
Binary, encoding kept3132
One-hot (full-vector compares), encoding kept5275
Binary written, no keep attribute5112

Measured on the pinned Yosys flow by the runs in this tree. In the last row the tool re-encoded the machine: read the flop count to see what it built.

A five-state sequencer, binary, encoding kept, with a default arm.

module seq5 (input logic clk, rst_n, start, last, output logic load, run, done);
  typedef enum logic [2:0] {IDLE, LOAD, RUN, DRAIN, DONE} state_e;   // binary 0 to 4
  (* fsm_encoding = "none" *) state_e st_q;
  state_e st_d;
  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n) st_q <= IDLE;
    else        st_q <= st_d;
  always_comb begin
    st_d = st_q;                      // stay, unless an arm moves
    case (st_q)
      IDLE:  if (start) st_d = LOAD;
      LOAD:  st_d = RUN;
      RUN:   if (last) st_d = DRAIN;
      DRAIN: st_d = DONE;
      DONE:  st_d = IDLE;
      default: st_d = IDLE;         // any unnamed code: back to IDLE
    endcase
  end
  assign load = (st_q == LOAD);
  assign run  = (st_q == RUN);
  assign done = (st_q == DONE);
endmodule

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Junior)

A good junior answer gives the flop count for each encoding. It says what one-hot makes cheap.

Cross-Questions

Where the interviewer goes next. Open a node to practise that follow-up.

WhyWhat IfCompareScale It

  1. Choosing a State Encoding
  2. WhyWhy One-Hot Decoding Is CheapL1 JuniorL2 Mid-Level

    Why can one-hot be fast, when the full-compare version above was not? Read this version.

    systemverilog
    module seq5 (input logic clk, rst_n, start, last, output logic load, run, done);
      localparam int IDLE = 0, LOAD = 1, RUN = 2, DRAIN = 3, DONE = 4;   // bit positions
      (* fsm_encoding = "none" *) logic [4:0] st_q;
      logic [4:0] st_d;
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) st_q <= 5'b00001;
        else        st_q <= st_d;
      always_comb begin
        st_d = '0;                        // each arm sets one bit
        unique case (1'b1)                // reverse case: each arm tests one state bit
          st_q[IDLE]:  if (start) st_d[LOAD] = 1'b1; else st_d[IDLE] = 1'b1;
          st_q[LOAD]:  st_d[RUN] = 1'b1;
          st_q[RUN]:   if (last) st_d[DRAIN] = 1'b1; else st_d[RUN] = 1'b1;
          st_q[DRAIN]: st_d[DONE] = 1'b1;
          st_q[DONE]:  st_d[IDLE] = 1'b1;
          default:     st_d[IDLE] = 1'b1;
        endcase
      end
      assign load = st_q[LOAD];
      assign run  = st_q[RUN];
      assign done = st_q[DONE];
    endmodule
    module tb;
      logic clk = 0, rst_n = 1, start = 0, last = 0, load, run, done;
      seq5 dut (.*);
      always #5 clk = ~clk;
      initial begin
        #1 rst_n = 0; #1 rst_n = 1;
        start = 1; @(posedge clk); #1 start = 0;
        @(posedge clk); #1 $display("two edges after start: load=%b run=%b", load, run);
        last = 1; @(posedge clk); #1 last = 0;
        @(posedge clk); #1 $display("two edges after last: done=%b", done);
        $finish;
      end
    endmodule
    
    Reveal the Model Answer

    Each arm tests one state bit, not the whole 5-bit vector. So "am I in RUN?" is one wire, and each next-state bit is a small OR of a few terms. On this flow it is 13 cells and 3 levels, against 27 and 5 for full-vector compares. The coding has to use the one-hot property for the encoding to save logic.

    RUNIcarus Verilog · in your browser
    Edit the code, then run it
    Expected: two edges after start: load=0 run=1 two edges after last: done=1

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "Writing enum values as one-hot is enough to get one-hot logic."

    A case on the whole vector still compares all bits, as the full-compare version shows. Testing single bits is what makes it cheap.

    Source Measured: this run and the root twin, Yosys 0.70+290 (OSS CAD Suite 2026-11-02), slang front end, synth -flatten with ABC, ltp -noff

  3. What IfWhen the Synthesizer Re-Encodes Your FSML2 Mid-LevelL3 Senior

    What if the tool does not keep your encoding? How would you notice, and when does it matter?

    Reveal the Model Answer

    Look at the flop count and the FSM section of the synthesis log. Here the binary machine came out with 5 flops: Yosys re-encoded it one-hot. That is often fine for speed. It matters when other logic reads the state code or the code crosses a clock domain. It also matters when recovery logic depends on it.

    module seq5 (input logic clk, rst_n, start, last, output logic load, run, done);
      typedef enum logic [2:0] {IDLE, LOAD, RUN, DRAIN, DONE} state_e;   // binary 0 to 4
      state_e st_q;
      state_e st_d;
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) st_q <= IDLE;
        else        st_q <= st_d;
      always_comb begin
        st_d = st_q;                      // stay, unless an arm moves
        case (st_q)
          IDLE:  if (start) st_d = LOAD;
          LOAD:  st_d = RUN;
          RUN:   if (last) st_d = DRAIN;
          DRAIN: st_d = DONE;
          DONE:  st_d = IDLE;
          default: st_d = IDLE;         // any unnamed code: back to IDLE
        endcase
      end
      assign load = (st_q == LOAD);
      assign run  = (st_q == RUN);
      assign done = (st_q == DONE);
    endmodule
    

    Self-Check

    Tick each key point you covered in your answer.

    Missed Corner

    "Re-encoding cannot change behaviour."

    It keeps behaviour only for the reachable states. Codes the machine never reaches from reset, and the arms for them, can disappear.

    Source Yosys 0.70+290 command help, help fsm_recode and help fsm_detect; lesson R3.2 measured netlists

  4. What IfA State Code That Crosses a Clock DomainL3 Senior

    What if the state register is read in another clock domain, and the tool re-encodes it?

    Reveal the Model Answer

    Then the reader may see a code that changes several bits at once, so it can sample a mix of two states. First, do not cross the state register at all. Cross a one-bit status through a synchronizer, or use a handshake. If you must keep the code, tell the tool not to re-encode it, and check the log after every run.

    Self-Check

    Tick each key point you covered in your answer.

    Missed Corner

    "Gray-code the states and the crossing is safe."

    Only if every transition changes one bit and the tool keeps the code. A branching or re-encoded machine can change several bits at once.

    Source Mehta, ASIC/SoC Functional Design Verification (2018) ch. 8.3.4 Multi-bit synchronization

  5. CompareGray-Coded States vs One-HotL2 Mid-Level

    Compare a Gray-coded state register with one-hot. When would you pick Gray?

    Reveal the Model Answer

    Pick Gray when the machine walks a fixed loop, like a counter, and you want one bit to change per step. Each step then toggles one flop instead of two. It also matters if the state is read in another clock domain, because a Gray step is never a torn value. One-hot is usually better for speed in a branching machine.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "You can safely synchronize a binary state register into another domain."

    Several bits can change in one step. The receiver can then catch a mix of old and new bits, which is a state that never existed.

    Source Mehta, ASIC/SoC Functional Design Verification (2018) ch. 8.3.4 Multi-bit synchronization

  6. Scale ItSplitting a Very Large FSML3 Senior

    Scale it up: a protocol controller has grown to 60 states and misses timing. What do you do?

    Reveal the Model Answer

    First look for hidden counters: chains of states that only count cycles. Replace them with one counter and one waiting state. Then split the rest into a main machine and small sub-machines that talk through start and done signals. Each piece gets fewer states, smaller next-state logic, and its own tests.

    Self-Check

    Tick each key point you covered in your answer.

    What Interviewers Listen For (Senior)

    A good senior answer looks for structure before it tunes encodings. It keeps each split machine testable on its own.

    Wrong Altitude

    "Try one-hot, then binary, and keep whichever passes."

    Encoding tweaks shave a level or two. A 60-state machine usually hides a counter or two machines, and fixing that removes the problem.

What People Get Wrong

Common wrong answers and why they fail. Run It shows the wrong belief failing on a real simulator.

Misconception

"One-hot always gives less logic and fewer levels."

A hand-written one-hot machine that compares the whole state vector is the worst here: 27 cells and 5 levels, against 13 and 2 for binary.

Run It

The sequencer one-hot with full-vector compares, then binary; both with the encoding kept.

module seq5 (input logic clk, rst_n, start, last, output logic load, run, done);
  typedef enum logic [4:0] {IDLE = 5'b00001, LOAD = 5'b00010, RUN = 5'b00100, DRAIN = 5'b01000, DONE = 5'b10000} state_e;
  (* fsm_encoding = "none" *) state_e st_q;
  state_e st_d;
  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n) st_q <= IDLE;
    else        st_q <= st_d;
  always_comb begin
    st_d = st_q;                      // stay, unless an arm moves
    case (st_q)
      IDLE:  if (start) st_d = LOAD;
      LOAD:  st_d = RUN;
      RUN:   if (last) st_d = DRAIN;
      DRAIN: st_d = DONE;
      DONE:  st_d = IDLE;
      default: st_d = IDLE;         // any unnamed code: back to IDLE
    endcase
  end
  assign load = (st_q == LOAD);
  assign run  = (st_q == RUN);
  assign done = (st_q == DONE);
endmodule
See the Fix
module seq5 (input logic clk, rst_n, start, last, output logic load, run, done);
  typedef enum logic [2:0] {IDLE, LOAD, RUN, DRAIN, DONE} state_e;   // binary 0 to 4
  (* fsm_encoding = "none" *) state_e st_q;
  state_e st_d;
  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n) st_q <= IDLE;
    else        st_q <= st_d;
  always_comb begin
    st_d = st_q;                      // stay, unless an arm moves
    case (st_q)
      IDLE:  if (start) st_d = LOAD;
      LOAD:  st_d = RUN;
      RUN:   if (last) st_d = DRAIN;
      DRAIN: st_d = DONE;
      DONE:  st_d = IDLE;
      default: st_d = IDLE;         // any unnamed code: back to IDLE
    endcase
  end
  assign load = (st_q == LOAD);
  assign run  = (st_q == RUN);
  assign done = (st_q == DONE);
endmodule

Tool as Truth

"The netlist uses the encoding I wrote in the enum."

Yosys re-encodes a detected FSM one-hot unless the state register has fsm_encoding = "none". Without it, this binary machine came out with 5 flops.

Source Measured: the sequencer without the attribute: 5 flops, 11 cells, Yosys 0.70+290 (OSS CAD Suite 2026-11-02), slang front end, synth -flatten with ABC, ltp -noff

Half-Truth

"Gray encoding saves power in every FSM."

Gray keeps one bit changing only along a fixed sequence of states. A machine that jumps between states loses that, and may need more logic.

Source Mehta, ASIC/SoC Functional Design Verification (2018) ch. 8.3.4 Multi-bit synchronization (one bit per Gray step)

Learn It, Then Prove It