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.
- 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
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.
- The datapath holds and changes data: registers, adders, comparators, counters.
- The controller is a state machine. Each cycle, it sends the datapath control signals - "load", "subtract", "clear".
- The datapath answers with status signals - "equal", "zero", "greater" - which the controller uses to choose its next state.
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:
- While the two numbers are different, take the smaller one away from the larger.
- 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.
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 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.
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.
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
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.
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.
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:
- The parent sets up the data, and raises a one-cycle start signal - here, send.
- The child does its many-cycle job, and shows busy = 1 while it works.
- The parent waits until the child is no longer busy, then moves on.
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.
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.
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:
- The sender puts the data out and raises req (request).
- The receiver sees req, takes the data, and raises ack (acknowledge).
- The sender sees ack, and lowers req.
- 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.
// ---------- 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.
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 |
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.
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:
- Gray code, when the value only ever steps to a neighbour - like a counter. Only one bit changes per step, so the receiver sees either the old value or the new one, never a mix. This is the reason for the Gray encoding in Volume 05.
- A handshake, for any data. The data travels on plain wires, held steady; only the request and the acknowledge - single bits - pass through synchronisers.
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:
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.
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.
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
- An FSMD splits a design into a datapath that works on data and a controller that decides; control signals go one way, status signals come back.
- An ASM chart draws a machine as a flowchart: state boxes, decision diamonds and action ovals, with one ASM block per clock cycle.
- A parent machine can start a child machine and wait for it - but must give it a cycle to start before checking busy.
- Merging machines multiplies their states; separate machines stay small and reusable.
- Four-phase handshakes are the safest, two-phase handshakes are faster, and valid/ready moves a word every cycle on one clock.
- Between clock domains, synchronise single bits, and move groups of bits with Gray code or a handshake - never bit by bit.
Key words from this volume
Every word below has a plain-English entry in the glossary.
- FSMD
- Datapath
- Control signal
- Status signal
- ASM chart
- Register transfer
- Hierarchical state machine
- State explosion
- Four-phase handshake
- Two-phase handshake
- Valid/ready handshake
- Clock domain
- Clock domain crossing (CDC)
- Metastability
- Synchroniser
Practice
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.
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.
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
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."
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.