Volume 09 Beginner 5 sub-modules ~25 min read

State Machines Working Together

Real designs are never just one state machine. A controller steers a datapath; one machine starts another and waits for it; two machines pass work back and forth; and sometimes they do not even share a clock. This volume shows the patterns engineers use to make many machines work as one.

You will learn
  • How a controller and a datapath share the work (FSMD)
  • How to read and draw an ASM chart
  • How one state machine starts another and waits for it
  • Four-phase, two-phase and valid/ready handshakes
  • How to pass signals safely between machines on different clocks
You need

9.1 Datapath plus controller (FSMD)

Big designs split into a datapath that does the work and a controller that decides, each cycle, what the datapath should do. The datapath reports back with status signals.

Two halves with two jobs

The lift in Volume 08 already had two halves. Registers held the floor and the calls; a state machine decided what to do next. This split is so common that it has a name: an FSMD, a finite state machine with a datapath.

Think of it like this

Think of a cook and a kitchen. The kitchen - knives, pans, the oven - does the work. The cook decides, step by step, what the kitchen does next, and keeps looking at the food to decide when a step is finished. The kitchen is the datapath; the cook is the controller.

An example: the greatest common divisor

The greatest common divisor (GCD) of two numbers is the largest number that divides both. The GCD of 21 and 6 is 3. There is a very old method that needs only subtraction:

  1. While the two numbers are different, take the smaller one away from the larger.
  2. When they are equal, that number is the GCD.

For 21 and 6: 21 - 6 = 15, then 15 - 6 = 9, then 9 - 6 = 3. Now 3 and 6: 6 - 3 = 3. Both are 3, so the GCD is 3.

In hardware, the datapath needs two registers, A and B, a subtractor, and a comparator. The controller needs three states: IDLE waits for start and loads the numbers, CALC repeats the subtraction, and DONE reports the result for one cycle.

A GCD calculator split into a controller and a datapath, with control signals going one way and status signals coming back Controller Datapath a state machine IDLE, CALC, DONE A B subtract compare ld, sub_a, sub_b eq, gt start done a_in, b_in result
Figure 9.1 - The GCD calculator as an FSMD. Control signals (gold) tell the datapath what to do this cycle; status signals (green) tell the controller what the data looks like.

The code: two halves, side by side


module gcd (
  input  wire       clk, rst, start,
  input  wire [7:0] a_in, b_in,
  output wire [7:0] result,
  output wire       done
);
  // ------------------------------------------------ controller
  localparam [1:0] IDLE = 2'd0, CALC = 2'd1, DONE = 2'd2;
  reg  [1:0] state, next_state;
  reg        ld, sub_a, sub_b;             // control signals, to the datapath
  wire       eq, gt;                       // status signals, from the datapath

  always @(posedge clk)
    if (rst) state <= IDLE;
    else     state <= next_state;

  always @(*) begin
    next_state = state; ld = 1'b0; sub_a = 1'b0; sub_b = 1'b0;
    case (state)
      IDLE: if (start) begin ld = 1'b1; next_state = CALC; end
      CALC: if (eq)      next_state = DONE;
            else if (gt) sub_a = 1'b1;     // a > b: a = a - b
            else         sub_b = 1'b1;     // a < b: b = b - a
      DONE: next_state = IDLE;
      default: next_state = IDLE;
    endcase
  end

  // ------------------------------------------------ datapath
  reg [7:0] a, b;
  always @(posedge clk)
    if (ld) begin a <= a_in; b <= b_in; end
    else begin
      if (sub_a) a <= a - b;
      if (sub_b) b <= b - a;
    end

  assign eq     = (a == b);
  assign gt     = (a > b);
  assign result = a;
  assign done   = (state == DONE);
endmodule

The controller never touches a number, and the datapath never makes a decision. Each half is small enough to check on its own.

The GCD calculator working out the GCD of 21 and 6 0 1 2 3 4 5 6 7 clk start state IDLE CALC DONE IDLE a 21 15 9 3 b 6 3 done
Figure 9.2 - The GCD of 21 and 6. In each CALC cycle the controller looks at eq and gt, and tells the datapath which register to reduce. When a and b are both 3, eq is 1, the controller moves to DONE, and done goes high for one cycle.
Common mistake

The datapath has limits that the controller must respect. Start this GCD with a 0 - say a = 0 and b = 5 - and b - a is 5 again, every cycle, for ever: the machine never reaches DONE. Always ask what inputs could stop your loop from ending, and check for them - or add a timeout from Volume 07.

Quick check

In the GCD calculator, which half decides that the calculation is finished?

Show the answer

Answer: B. The datapath only reports facts - here, whether a equals b. The controller reads that status signal and makes the decision, by moving from CALC to DONE.

9.2 ASM charts

An ASM chart draws a state machine like a flowchart: a box for each state, diamonds for decisions, and ovals for the actions that happen on the way.

Why another kind of drawing?

A state diagram is perfect for a pure controller. But in an FSMD, the interesting part is often what happens to the data - "subtract b from a" - and a state diagram has no good place to write it. An ASM chart (algorithmic state machine chart) looks like a flowchart, so it reads like the method itself.

The three shapes

Shape Name Holds
Rectangle state box the state's name and its Moore outputs
Diamond decision box a question about an input or a status signal, with a 0 exit and a 1 exit
Oval conditional output box a Mealy output or an action that happens only on that path

An action written like a ← a − b is a register transfer: at the next clock edge, register a takes the value a − b.

ASM chart of the GCD calculator with state boxes, decision diamonds and conditional output ovals IDLE CALC DONE done = 1 start a = b a > b ld = 1 a ← a − b b ← b − a 0 1 1 0 1 0 one ASM block = one clock cycle
Figure 9.3 - The GCD calculator as an ASM chart. Rectangles are states, diamonds are decisions, and ovals are actions on a path. The dashed outline is one ASM block: everything inside it happens in a single clock cycle.

How to read it

Start at IDLE. Follow the arrow into the start diamond. If start is 0, the path loops back to IDLE: the machine waits. If start is 1, the path passes through the oval "ld = 1" - load the numbers - and arrives at CALC.

From CALC, the path meets the diamond a = b. If they are equal, it goes to DONE. If not, it asks a > b, and takes the matching oval - a ← a − b or b ← b − a - before returning to CALC.

The one rule of ASM charts

Remember

An ASM block is one state box plus every decision and oval below it, up to the next state boxes. Everything in one block happens in one clock cycle. Decisions take no time; only the step from one state box to the next does.

So the chart in Figure 9.3 says this. In each CALC cycle, look at a and b, do one subtraction, and stay in CALC. If they are equal, go to DONE instead. That is exactly what the Verilog does.

Common mistake

Treating each diamond as a clock cycle. A diamond is not a state; it is a decision made by gates within the current cycle. If you count diamonds as cycles, you will expect the GCD to take three times as long as it really does.

Quick check

In an ASM chart, where do you write a Mealy output?

Show the answer

Answer: C. A Moore output belongs to the state, so it goes in the state box. A Mealy output depends on the inputs too, so it is written in an oval after the decisions that lead to it - on the path, not in the state.

9.3 Hierarchical and nested FSMs

When one step hides a whole sequence of smaller steps, give it its own small state machine. The big machine starts it and waits for it to finish, like a program calling a function.

Machines that call machines

In Volume 08 you built a UART transmitter. It sends one byte. Now suppose a design must send a whole message - "HELLO". You could add five times as many states to the transmitter. Or you can leave the transmitter alone, and add a small parent machine that uses it, one character at a time. This is a hierarchical state machine.

The conversation between them is always the same:

  1. The parent sets up the data, and raises a one-cycle start signal - here, send.
  2. The child does its many-cycle job, and shows busy = 1 while it works.
  3. The parent waits until the child is no longer busy, then moves on.
The parent state machine that sends HELLO one character at a time IDLE LOAD WAIT go always done, more done, last else busy reset
Figure 9.4 - The parent machine. LOAD starts the child - the UART transmitter - with one character. WAIT stays until the child has finished, then either sends the next character or, after the last one, returns to IDLE.

module send_msg (
  input  wire clk, rst, go,
  output wire tx                               // the serial line
);
  localparam [1:0] IDLE = 2'd0, LOAD = 2'd1, WAIT = 2'd2;
  reg  [1:0] state;
  reg  [2:0] idx;                              // which character, 0 to 4
  reg        send;                             // start pulse to the child
  wire       busy;                             // from the child
  reg  [7:0] ch;

  always @(*)                                  // the message, one character at a time
    case (idx)
      3'd0: ch = "H";  3'd1: ch = "E";  3'd2: ch = "L";  3'd3: ch = "L";
      default: ch = "O";
    endcase

  uart_tx #(.CPB(434)) child (.clk(clk), .rst(rst), .send(send), .data(ch),
                              .tx(tx), .busy(busy));

  always @(posedge clk) begin
    send <= 1'b0;
    if (rst) begin state <= IDLE; idx <= 3'd0; end
    else case (state)
      IDLE: if (go) begin idx <= 3'd0; state <= LOAD; end
      LOAD: begin send <= 1'b1; state <= WAIT; end            // start the child
      WAIT: if (!send && !busy) begin                          // the child has finished
              if (idx == 3'd4) state <= IDLE;
              else begin idx <= idx + 1; state <= LOAD; end
            end
    endcase
  end
endmodule

The line uart_tx #(.CPB(434)) child (...) places a copy of the Volume 08 transmitter inside this module and names it child. The transmitter is not changed at all.

The trap in WAIT

Look again at the WAIT condition: !send && !busy. Why check send?

In the first cycle of WAIT, send has only just become 1. The child sees it at the next edge, so in that first cycle the child is still idle, and busy is still 0. A parent that checked only !busy would think the child had already finished, and hurry on to the next character - while the child is only now starting on this one. A careful model of the design shows the result: the line carries "HLO", with every second character lost. The !send makes the parent wait one cycle, until busy has had time to rise.

Remember

After starting a child machine, never check its "done" or "not busy" in the very next cycle. Give the child a cycle to start - or use a child that raises a separate one-cycle done pulse.

Why not one big machine?

You could merge the parent and the child into one machine. But the states multiply: every parent state combined with every child state. A parent with 3 states and a child with 4 gives up to 12 states - and real designs have many more. This fast growth is called state explosion. Separate machines stay small, can each be tested alone, and can be reused - the same uart_tx could serve ten different parents.

Going deeper: nested states in statecharts

Some design tools draw states inside states. A big state WASHING might contain small states FILL, WASH and RINSE. One arrow out of the big state, for "stop pressed", then works from any of the small states. These drawings are called statecharts. They are a neat way to draw a hierarchy, but in hardware they still become either one merged machine or separate machines, as in this sub-module.

Quick check

A parent machine raises start for one cycle, then checks the child's busy signal in the very next cycle. What can go wrong?

Show the answer

Answer: A. The child only sees start at the next clock edge. In the cycle straight after start, it has not reacted yet, so busy is still 0 - which looks exactly like "finished". The parent must wait a cycle, or use a separate done pulse.

9.4 Request/acknowledge handshakes

When two machines pass work between them, they use a handshake. One says "here is something", the other says "I have taken it", and neither moves on until the other has answered.

The four-phase handshake

The most robust handshake has four steps, and ends with both signals back at 0. It is called the four-phase handshake:

  1. The sender puts the data out and raises req (request).
  2. The receiver sees req, takes the data, and raises ack (acknowledge).
  3. The sender sees ack, and lowers req.
  4. The receiver sees req fall, and lowers ack. Both are at 0 again, ready for the next transfer.

Two rules make it safe. The sender must not change the data while req is 1. And the receiver takes the data exactly once per request - when req rises - not on every cycle that req is high.

A four-phase handshake passing two words, A and B, between two state machines 0 1 2 3 4 5 6 7 8 9 10 11 clk sender IDLE REQ DROP IDLE REQ DROP IDLE req data A B ack receiver WAIT GOT WAIT GOT WAIT
Figure 9.5 - Two words passed with a four-phase handshake. Each transfer walks through all four steps: req up, ack up, req down, ack down. The data is held steady for the whole time req is 1.

// ---------- sender: IDLE -> REQ -> DROP -> IDLE
always @(posedge clk)
  if (rst) begin s_state <= IDLE; req <= 1'b0; end
  else case (s_state)
    IDLE: if (have_word) begin data <= word; req <= 1'b1; s_state <= REQ; end
    REQ:  if (ack)  begin req <= 1'b0; s_state <= DROP; end    // taken: release
    DROP: if (!ack) s_state <= IDLE;                          // handshake complete
  endcase

// ---------- receiver: WAIT -> GOT -> WAIT
always @(posedge clk)
  if (rst) begin r_state <= WAIT; ack <= 1'b0; end
  else case (r_state)
    WAIT: if (req)  begin got <= data; ack <= 1'b1; r_state <= GOT; end  // take it once
    GOT:  if (!req) begin ack <= 1'b0; r_state <= WAIT; end
  endcase

A four-phase transfer takes several cycles: here, five cycles per word. The price buys safety - each side waits for the other at every step, whatever their speeds.

The two-phase handshake

A two-phase handshake saves time by counting changes instead of levels. Every change of req - up or down - is a new request. Every change of ack answers it. When req and ack are equal, the channel is free.

A two-phase handshake passing five words, one every two cycles 0 1 2 3 4 5 6 7 8 9 clk req ack data A B C D E
Figure 9.6 - A two-phase handshake. A new word goes out whenever req equals ack, by flipping req. The receiver answers by flipping ack. No step is spent returning to 0, so a word moves every two cycles.

It is twice as fast, but the logic must remember the last value of each signal and compare - a little harder to get right.

Valid and ready: the same-clock handshake

When both machines share one clock, there is an even faster pattern. The sender raises valid when it has data; the receiver raises ready when it can take data. The data moves in every cycle in which both are 1 - one word per cycle at full speed. This is the valid/ready handshake, used by AXI-Stream and countless internal buses. It is covered in depth in the VALID/READY handshake in the FPGA course.

Handshake Transfers Best for
Four-phase one per four signal changes safety, and links between different clocks
Two-phase one per two signal changes faster links between different clocks
Valid/ready one per clock cycle machines on the same clock
Common mistake

A receiver that takes the data on every cycle that req is 1. In the waveform, req stays high for two cycles, so that receiver would take word A twice. Take the data once, when the handshake moves on - in WAIT - and let GOT simply wait for req to fall.

Quick check

In a four-phase handshake, when may the sender change the data for the next word?

Show the answer

Answer: D. The receiver may take the data at any moment while req is 1. So the data must stay steady until the handshake is over; the sender changes it only when it raises req again for the next word.

9.5 FSMs across two clocks

When two machines run on different clocks, every signal passing between them must be synchronised, and a group of bits must travel with a handshake - never bit by bit.

Two clocks, one problem

A chip often has several clocks: a fast one for the processor, a slower one for a peripheral, one for each external link. All the flip-flops on one clock form a clock domain. A signal that travels from one domain to another is a clock domain crossing, or CDC.

The trouble is timing. The receiving flip-flops do not know when the signal will change, so sooner or later it changes right at their clock edge. A flip-flop caught like that may hover between 0 and 1 for a moment before it settles. This is metastability. If that half-value spreads into a state machine, the machine can jump to a wrong state - even an illegal one.

Rule 1: synchronise every single-bit signal

The cure for one bit is the two-flip-flop synchroniser from Volume 07. The first flip-flop may go metastable, but it has a whole clock cycle to settle before the second one reads it. The chance that it is still undecided a cycle later is astronomically small. The full story, with the numbers, is in metastability and MTBF in the Verilog course.

Rule 2: never synchronise a group of bits one by one

Suppose a state machine sends its 3-bit state to another domain, through three synchronisers. If the state changes from 011 to 100, all three bits change. Each synchroniser may catch its bit a cycle earlier or later than the others. So for one cycle the receiver may see 111, 000 or any other mix - a value that was never sent.

There are two safe ways to pass a group of bits:

The handshake across two clocks

This is the four-phase handshake of sub-module 9.4, with one synchroniser on req and another on ack:

A four-phase handshake between two clock domains: req and ack each pass through a two-flip-flop synchroniser, while the data crosses on plain wires held steady Sender Receiver clock A domain clock B domain state machine state machine req ack data, held steady while req = 1
Figure 9.7 - Passing data between clock domains. req and ack are single bits, so each goes through its own two-flip-flop synchroniser (the small squares). The data does not: the sender holds it steady from before req rises until ack comes back, so it is never read while changing.

Each side's state machine is exactly the one from sub-module 9.4. The only change is that each side reads the other's signal through its synchroniser - so each step of the handshake takes a couple of extra cycles. That is the cost of crossing safely.

Common mistake

Sending a one-cycle pulse from a fast clock domain to a slow one. The pulse may begin and end between two edges of the slow clock, so the slow side never sees it at all. Use a handshake, turn the pulse into a level change (a toggle), or stretch it with a pulse stretcher from Volume 07.

Quick check

A 4-bit counter's value must be read by a machine on another clock. Which is safe?

Show the answer

Answer: B. With a binary count, several bits can change at once, and separate synchronisers may catch them in different cycles, giving a value that was never there. In Gray code only one bit changes per step, so the reader always sees either the old value or the new one.

What you learned

Key words from this volume

Every word below has a plain-English entry in the glossary.

Practice

Practice 1

Trace the GCD

Trace the GCD calculator for a = 12 and b = 18. List a and b in each CALC cycle, and say how many cycles pass from the start pulse to done.

Show the solution
Cycle State a b Action
0 IDLE - - start: load
1 CALC 12 18 a < b, so b = b - a
2 CALC 12 6 a > b, so a = a - b
3 CALC 6 6 equal: go to DONE
4 DONE 6 6 done = 1, result = 6

The GCD is 6, and done rises 4 cycles after the start pulse.

Practice 2

Add a zero check

Change the GCD controller so that it finishes at once, with result 0, if either input is 0.

Show the solution

Add one status signal from the datapath, zero = (a == 0) || (b == 0), and check it first in CALC:


CALC: if (eq || zero) next_state = DONE;     // equal, or nothing to do
      else if (gt)    sub_a = 1'b1;
      else            sub_b = 1'b1;

Strictly, the GCD of 0 and 5 is 5, but many designs simply report such inputs as invalid. Either way, the point is the same: the controller must never enter a loop that cannot end.

Practice 3

Count the merged states

A parent machine has 4 states and uses two children, one with 3 states and one with 5. If all three are merged into one machine, how many states could it need?

Show the solution

Every combination of the three machines' states: 4 × 3 × 5 = 60 states, against 4 + 3 + 5 = 12 states for the three separate machines. Not every combination may be reachable, but the merged machine is still far larger and harder to check - state explosion.

Interview corner

Interview question 1

Controller and datapath

"How would you structure a design that multiplies two numbers by repeated addition?"

Show the solution

"As an FSMD. The datapath has a register for the running total, a register for the count, an adder and a zero check. The controller has three states. IDLE loads the operands and clears the total on start. ADD adds the multiplicand to the total and decrements the count each cycle, until the zero status is 1. DONE reports the result for one cycle. The controller only makes decisions, and the datapath only does arithmetic, so each is easy to verify on its own."

Interview question 2

Crossing clock domains

"You need to pass a 32-bit value from one clock domain to another. How?"

Show the solution

"Never through 32 separate synchronisers - the bits could arrive in different cycles and give a value that was never sent. I would use a handshake. The sender holds the 32 bits steady and raises req, which is synchronised into the receiving domain. The receiver captures the data, which has been stable for several cycles, and raises ack, which is synchronised back. For a continuous stream of data I would use an asynchronous FIFO instead, which passes Gray-coded pointers across the boundary."

Next, Volume 10 asks a different question: is your machine as small as it could be? It shows how to find states that do the same job, and merge them.