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
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
    • Controller and Datapath
      • Split Design: Intent First
      • Signal Contract Between FSM and Datapath
      • Two-Process FSM Style
      • Coding Rules That Prevent Latent Bugs
      • Worked Example: GCD
      • FSM Bug Stories
    • Moore vs Mealy
    • State Encoding
    • Multi-Cycle Sequencers
    • UART Transmitter
    • Memory Controller
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Controller and Datapath
  1. Topics
  2. RTL Design
  3. FSM & Control
  4. Controller and Datapath
  5. Worked Example: GCD

Part 4 · FSM & Control · Intermediate

Worked Example: GCD

A tested subtract-and-compare GCD controller and datapath.

On this page

    Why this matters

    A subtract-based GCD never finishes if one operand is zero. The example also shows how load, subtract and done cycles line up between the controller and the datapath.

    After this lesson you can: build a GCD from a datapath and an FSM, read its run across the states, and predict the edge on which done rises.

    The design

    The datapath holds a and b with load and subtract operations. Two status flags report greater-than and equal.

    The controller waits for start, subtracts until the operands are equal, then reports done. This listing requires both inputs to be at least 1; with exactly one zero operand, subtraction makes no progress.

    A simulated run

    Watch state, a, b, and done across the nine slots. The slot table below it names the control behind each step.

    • Slot 1: idle in IDLE, start low, operands unknown.

    • Slot 2: start rises, and IDLE answers with load.

    • Slot 3: RUN with a = 12 and b = 8, so a > b selects sub_a.

    • Slot 4: a = 4 and b = 8, so a < b selects sub_b.

    • Slot 5: b = 4 and the operands match, so next is DONE.

    • Slot 6: DONE with done high and result 4.

    • Slot 7: back to IDLE with done low.

    • Slot 8: idle.

    • Slot 9: idle.

    Slot

    State

    a

    b

    Flags

    Control

    2

    IDLE

    x

    x

    –

    load (start seen)

    3

    RUN

    12

    8

    a > b

    sub_a

    4

    RUN

    4

    8

    a < b

    sub_b

    5

    RUN

    4

    4

    a = b

    – (next = DONE)

    6

    DONE

    4

    4

    a = b

    done

    Common mistakes

    Common pitfall

    With exactly one zero operand, the loop never terminates: subtracting zero makes no progress. Keep the positive-input restriction used here, or extend both control and datapath: after loading, report zero flags to the controller, enter DONE when either stored operand is zero, and select result = (a == 0) ? b : a. Merely entering DONE is insufficient because the current result = a would return 0 for (0, 5) instead of 5. For that extension, define (0, 0) to return 0 as an interface convention (or explicitly reject it), and add directed cases (0, 5), (5, 0) and (0, 0) for result and termination. The listing and test below cover positive inputs only.

    Tip

    Performance note: the subtract-based algorithm needs up to max(a,b) iterations. For long operands, use a binary GCD (shift and subtract) or division-based Euclid to cut the cycle count.

    Edge

    Before edge / action

    After edge settles

    0 (accept)

    IDLE, start=1: load

    RUN, a=12, b=8, done=0

    1

    RUN, a>b: sub_a

    RUN, a=4, b=8, done=0

    2

    RUN, a<b: sub_b

    RUN, a=4, b=4, done=0

    3

    RUN, equal: enter DONE

    DONE, done=1, result=4

    4 (consume)

    Consumer samples done=1 and result=4

    IDLE, done=0, result held

    Check yourself How many cycles after acceptance is gcd(12, 8) done? When can a rising-edge consumer sample it?

    Done asserts after edge 3, three periods after the edge-0 load. A consumer sampling pre-update values at rising edges sees it at edge 4. Equality is already combinational after edge 2; there is no extra output-register cycle.

    Testbench

    The complete width-8 design and bounded checker follow: 961 positive pairs in 1..31, plus directed interface cases.

    gcd_dp.sv
    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
    module gcd_dp #(parameter int W = 8) (
      input  logic         clk,
      input  logic         load, sub_a, sub_b,
      input  logic [W-1:0] a_in, b_in,
      output logic         a_gt_b, a_eq_b,
      output logic [W-1:0] result
    );
      logic [W-1:0] a, b;
      always_ff @(posedge clk)
        if (load)        begin a <= a_in; b <= b_in; end
        else if (sub_a)  a <= a - b;
        else if (sub_b)  b <= b - a;
      assign a_gt_b = (a > b);
      assign a_eq_b = (a == b);
      assign result = a;
    endmodule
    
    module gcd_ctrl (
      input  logic clk, rst_n,
      input  logic start, a_gt_b, a_eq_b,
      output logic load, sub_a, sub_b, done
    );
      typedef enum logic [1:0] {IDLE, RUN, DONE} state_t;
      state_t state, next;
    
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) state <= IDLE;
        else        state <= next;
    
      always_comb begin
        next = state;  load = 1'b0;  sub_a = 1'b0;  sub_b = 1'b0;  done = 1'b0;
        unique case (state)
          IDLE: if (start) begin load = 1'b1; next = RUN; end
          RUN:  if (a_eq_b)       next  = DONE;
                else if (a_gt_b)  sub_a = 1'b1;
                else              sub_b = 1'b1;
          DONE: begin done = 1'b1; next = IDLE; end
          default: next = IDLE;
        endcase
      end
    endmodule
    
    module gcd_top #(parameter int W = 8) (
      input  logic clk, rst_n, start,
      input  logic [W-1:0] a_in, b_in,
      output logic done,
      output logic [W-1:0] result
    );
      logic load, sub_a, sub_b, a_gt_b, a_eq_b;
      gcd_ctrl u_ctrl (.*);
      gcd_dp #(W) u_dp (.*);
    endmodule
    
    • Every pair of operands from 1 to 31 gives the correct GCD, compared with a software reference, including cases where one operand is a multiple of the other.

    • A 100-cycle timeout rejects hangs for the enumerated positive pairs.

    • Back-to-back operations work: the machine returns to IDLE after done and accepts the next start.

    • Directed checks exercise changed operands and start while busy, an ignored DONE offer, exact load-to-done edges, one-cycle done, result hold and reset cancellation followed by a fresh operation. Errors identify time, context, state and controls and end in a fatal verdict.

    Remember

    • IDLE loads on start, RUN subtracts the smaller from the larger, and DONE reports the result.

    • This listing needs both inputs at least 1; with one zero operand subtraction makes no progress.

    • The subtract algorithm needs up to max(a,b) iterations.

    How this was checked

    The bounded checker covers 961 positive pairs in 1..31 and directed interface cases. The 100-cycle timeout rejects hangs for the enumerated pairs; it is not a proof of termination for every width or input.

    The 1 ns post-edge samples use a 10 ns teaching clock. They are simulation scheduling margins, not physical delays.

    Keep going

    • Interview: run a mock interview on this topic

    Related topics

    • Multi-cycle sequencers

    Previous

    93 / 350

    Next: FSM Bug Stories

    Up next

    Up nextFSM Bug Stories

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Split Design: Intent First
    • Signal Contract Between FSM and Datapath
    • Two-Process FSM Style
    gcd(12, 8), state values IDLE=0, RUN=1, DONE=2; sampled just before each clock edgeclkstartstateIDLERUNDONEIDLEa (hex)xc4b (hex)x84done
    Start loads 12 and 8. In RUN, a > b, so a becomes 12 − 8 = 4; next, b becomes 8 − 4 = 4; equal, so the machine goes to DONE, and done is high for one cycle with result 4.