Skip to content

5.5 FSMs in SystemVerilog

Why this matters

Once the diagram and the equations are right, turning them into SystemVerilog should be mechanical, and it can be, as long as every machine follows the same four-part shape. The shape also guards against the two bugs that cost the most time in this chapter: a latch that appears because one path forgot to assign the next state, and an output that changes at a different moment than you meant.

  • Write a state machine as a module with the four-part pattern, with the next-state logic as a case on the present state.
  • Match each arrow of a diagram to a line of the next-state logic.
  • Say whether an output is Mealy or Moore from the line of code that computes it.
  • Explain why the default arm of the next-state case is needed, and recognize the latch that appears without it.
  • Read a next state written as a conditional expression.

Section 5.3 drew a machine as a register that holds the present state, and a block of combinational logic that looks at the register and the inputs and produces the next state and the outputs. The SystemVerilog has exactly those pieces, written one after another, so a reader can find each by name. One part declares what the register's contents mean. Two parts describe the combinational logic. One part is the register itself. If you can point at each of them in the picture, you can point at it in the code.

For a programmer, the first three parts are a pure function from (state, inputs) to (next state, outputs), and the last part is the assignment that carries the result into the next loop iteration. The analogy breaks in the usual place: the function is not called, it is always there, and the assignment happens at every clock edge whether or not anyone asks.

Definition 5.5.1(the four-part FSM pattern).

A state machine in SystemVerilog is written in four parts, in this order. State encodings and variables: an enum that names the states and gives them codes, with the present state ps and the next state ns declared in it. Next-state logic: an always_comb block that assigns ns from ps and the inputs, with blocking assignments. Output logic: an assign or an always_comb that computes the outputs. State update: an always_ff block that loads ps from ns at the rising clock edge, or with a reset puts it in the initial state, with nonblocking assignments.

Section 5.2 showed this shape in the listing of the long-press detector. Every machine here follows it, and the sections below add what that listing did not need.

Example 5.5.1(the vending machine as a module).

The gumball machine of Section 5.4 has two states, 0¢ and 5¢, two inputs N and D, and one output Open. Here are its four parts, one at a time. The whole file is a listing under the figures that follow.

The declarations name the states with the codes of the truth table: 0 for 0¢ and 1 for 5¢. The state names in the file, C0 and C5, are the circles of the diagram, so a reader can compare the two. The enum has one bit, so ps and ns have one bit.

vending_machine.sv
// C0 means no money waiting, C5 means one nickel waiting.
enum logic {C0 = 1'b0, C5 = 1'b1} ps, ns;

The next-state logic has one arm of the case for each circle. Inside an arm, an if chooses between the circle's arrows. In C0 only a nickel leaves the state, so the test is N. In C5 a nickel or a dime leaves it, and one condition, N | D, stands for both arrows. The default arm makes sure every path through the block assigns ns. With one state bit and both codes used, no real state reaches it; in simulation it runs only before the first reset, while ps is still unknown. It costs nothing, and it states the decision in the code: whatever ps holds, ns gets a value, so no path leaves ns unassigned (the latch problem below).

vending_machine.sv
always_comb
case (ps)
C0: if (N) ns = C5;
else ns = C0;
C5: if (N | D) ns = C0;
else ns = C5;
default: ns = C0;
endcase

The output is the equation from Section 5.4, written as an assign. It reads D and N as well as the state, which makes it a Mealy output.

vending_machine.sv
assign Open = D | ((ps == C5) & N);

The state update is the only place where anything is remembered. At every rising edge, ps takes the value of ns, unless reset is 1, in which case it takes the initial state. The assignment uses <= because this block is clocked.

vending_machine.sv
always_ff @(posedge clk)
if (reset) ps <= C0;
else ps <= ns;

A testbench in the book's usual shape runs the module. It resets, puts in two nickels, a dime, and then a nickel followed by a dime. As in Section 5.2, each // expect: comment gives the state and the output just before the edge that ends its line, and the book's tools check every comment against a simulation of the module and against the machine of Section 5.4.

vending_machine_tb.sv
// Testbench for vending_machine: reset, two nickels, a dime, then a nickel followed by a dime.
module vending_machine_tb();
logic clk, reset, N, D, Open;
vending_machine dut(.*);
parameter CLOCK_PERIOD = 100;
initial begin
clk <= 0;
forever #(CLOCK_PERIOD / 2) clk <= ~clk;
end
initial begin
reset <= 1; N <= 0; D <= 0; @(posedge clk);
reset <= 0; @(posedge clk); // expect: ps=C0, Open=0
N <= 1; @(posedge clk); // expect: ps=C0, Open=0
@(posedge clk); // expect: ps=C5, Open=1
N <= 0; @(posedge clk); // expect: ps=C0, Open=0
D <= 1; @(posedge clk); // expect: ps=C0, Open=1
D <= 0; @(posedge clk); // expect: ps=C0, Open=0
N <= 1; @(posedge clk); // expect: ps=C0, Open=0
N <= 0; D <= 1; @(posedge clk); // expect: ps=C5, Open=1
D <= 0; @(posedge clk); // expect: ps=C0, Open=0
@(posedge clk); // expect: ps=C0, Open=0
$stop;
end
endmodule
Waveform ch05/vending-trace-a: clk, reset, N, D, dut.ps, OpenclkresetNDdut.psC0C5C0Open050100150200250300350400450500550600650time (ps)Waveform ch05/vending-trace-a: clk, reset, N, D, dut.ps, OpenclkresetNDdut.psC0C5C0Open050100150200250300350400450500550600650time (ps)Waveform ch05/vending-trace-a: clk, reset, N, D, dut.ps, OpenclkresetNDdut.psC0C5C0Open0100200300400500600time (ps)Waveform ch05/vending-trace-a: clk, reset, N, D, dut.ps, OpenclkresetNDdut.psC0C5C0Open0100200300400500600time (ps)Waveform ch05/vending-trace-a: clk, reset, N, D, dut.ps, OpenclkresetNDdut.psC0C5C0Open0100200300400500600time (ps)

Time in ps; one column is 50 ps.

Timing diagram from simulation, ch05/vending-trace-a: time in ps from 0 to 700, 14 columns of 50 ps.

clk: clock, period 100 ps, starts 0, 7 rising edges in the window (50, 150, 250, ...).

reset: 1 at 0, then 0 at 50.

N: 0 at 0, then 1 at 150, 0 at 350, 1 at 650.

D: 0 at 0, then 1 at 450, 0 at 550.

dut.ps (state names: C0, C5): unknown at 0, then C0 at 50, C5 at 250, C0 at 350.

Open: 0 at 0, then 1 at 250, 0 at 350, 1 at 450, 0 at 550.

Dashed vertical guides mark the rising clock edges.

Description: A timing diagram of clk, reset, N, D, the state dut.ps and Open for the first seven clock cycles of the testbench. The state is unknown before the first rising edge and C0 after it.

Figure 5.5.1. The first seven cycles of the run: reset, two nickels, and a dime. Open rises with the second nickel and again with the dime.
Waveform ch05/vending-trace-b: clk, reset, N, D, dut.ps, OpenclkresetNDdut.psC0C5C0Open3504004505005506006507007508008509009501000time (ps)Waveform ch05/vending-trace-b: clk, reset, N, D, dut.ps, OpenclkresetNDdut.psC0C5C0Open350450550650750850950time (ps)Waveform ch05/vending-trace-b: clk, reset, N, D, dut.ps, OpenclkresetNDdut.psC0C5C0Open350450550650750850950time (ps)Waveform ch05/vending-trace-b: clk, reset, N, D, dut.ps, OpenclkresetNDdut.psC0C5C0Open350450550650750850950time (ps)Waveform ch05/vending-trace-b: clk, reset, N, D, dut.ps, OpenclkresetNDdut.psC0C5C0Open350550750950time (ps)

Time in ps; one column is 50 ps.

Timing diagram from simulation, ch05/vending-trace-b: time in ps from 350 to 1050, 14 columns of 50 ps.

clk: clock, period 100 ps, starts 1, 6 rising edges in the window (450, 550, 650, ...).

reset: 0 at 350, no changes.

N: 0 at 350, then 1 at 650, 0 at 750.

D: 0 at 350, then 1 at 450, 0 at 550, 1 at 750, 0 at 850.

dut.ps (state names: C0, C5): C0 at 350, then C5 at 750, C0 at 850.

Open: 0 at 350, then 1 at 450, 0 at 550, 1 at 750, 0 at 850.

Dashed vertical guides mark the rising clock edges.

Description: A timing diagram of clk, reset, N, D, the state dut.ps and Open for the later cycles of the testbench.

Figure 5.5.2. The second part of the run, which starts before the first part ends: after the dime come a nickel and then a dime that arrives while the machine is waiting with 5 cents.

How thoroughly to test the machine is the subject of Section 5.6. For now the run is only a demonstration.

The output logic is where a Mealy machine and a Moore machine differ in code. If the line that computes an output reads an input, or reads ns (which depends on the inputs), the output is Mealy. If it reads only ps, the output is Moore.

To compare the two, take a small machine that watches In for the sequence 1, 0, 1. Its output is 1 in a cycle in which In is 1, was 0 in the cycle before and was 1 in the cycle before that. Occurrences may overlap: the last 1 of one can be the first 1 of the next, so five cycles of 1, 0, 1, 0, 1 give two detections. After a reset the machine has seen no input yet.

The Mealy version needs three states, named for what the last inputs were: Idle, in which no part of the sequence is waiting; One, in which the last input was 1; and OneZero, in which the last two inputs were 1 and then 0. Only in OneZero can a 1 on the input finish the sequence, and then the machine goes to One, because that 1 may start the next occurrence.

State diagram of s05-detect-mealyState diagram of s05-detect-mealy: a Mealy machine with 3 states (Idle, One, OneZero). The initial state is Idle, marked by an arrow that comes from no state. From Idle when In=0, output 0, go to Idle. From Idle when In=1, output 0, go to One. From One when In=1, output 0, go to One. From One when In=0, output 0, go to OneZero. From OneZero when In=1, output 1, go to One. From OneZero when In=0, output 0, go to Idle. Inputs, in label order: In. Outputs: Out.0/01/01/00/01/10/0IdleOneOneZerostart

Description: State diagram with three circles Idle, One and OneZero in a row. A starting arrow enters Idle. Idle has a self-loop for 0 with output 0 and an arrow to One for 1 with output 0. One has a self-loop for 1 with output 0 and an arrow to OneZero for 0 with output 0. OneZero has an arrow back to One for 1 with output 1 and a longer arrow back to Idle for 0 with output 0.

Figure 5.5.3. The Mealy detector. The only arrow with output 1 is the one from OneZero for a 1 on the input.

The output is raised when the machine is in OneZero and In is 1, so its output logic reads the input:

detect_mealy.sv
// The output reads the input In directly, so it is a Mealy output.
assign Out = (ps == OneZero) & In;

The Moore version cannot look at In in its output, so it needs a state of its own for a detection. It has a fourth state, Found, which the machine enters at the clock edge that ends the cycle in which the last 1 of the sequence was on the input. It raises Out while it is in Found, which depends on the state alone. From Found the arrows go where they go from One, because in both states the last input was 1:

State diagram of s05-detect-mooreState diagram of s05-detect-moore: a Moore machine with 4 states (Idle, One, OneZero, Found). The initial state is Idle, marked by an arrow that comes from no state. From Idle when In=0, go to Idle. From Idle when In=1, go to One. From One when In=1, go to One. From One when In=0, go to OneZero. From OneZero when In=1, go to Found. From OneZero when In=0, go to Idle. From Found when In=0, go to OneZero. From Found when In=1, go to One. State Idle outputs 0. State One outputs 0. State OneZero outputs 0. State Found outputs 1. Inputs, in label order: In.01101001Idle0One0OneZero0Found1start

Description: State diagram with four circles Idle, One, OneZero and Found in a row. Idle, One and OneZero have output 0 and Found has output 1. A starting arrow enters Idle. Idle has a self-loop for 0 and an arrow to One for 1. One has a self-loop for 1 and an arrow to OneZero for 0. OneZero has an arrow to Found for 1, and a longer arrow back to Idle for 0. Found has an arrow back to OneZero for 0 and a longer arrow back to One for 1.

Figure 5.5.4. The Moore detector. The output is written under the state name, and only Found has output 1.
detect_moore.sv
// The output reads the state only, so it is a Moore output.
assign Out = (ps == Found);

The two machines give outputs one cycle apart. For the input 1, 0, 1, 0, 1, 0 the Mealy version gives this output. These are scenario tables, so cycle 0 is the first cycle after reset, as in Section 5.4:

Scenario overlap of ch05/s05-detect-mealy, 6 cycles, cycle 0 first. Inputs: In is 1, 0, 1, 0, 1, 0. State in each cycle: Idle, One, OneZero, One, OneZero, One. Out in each cycle: 0, 0, 1, 0, 1, 0.
Cycle012345
In101010
StateIdleOneOneZeroOneOneZeroOne
Out001010

Cycle 0 is the first cycle after reset: the machine starts in Idle, and each cycle ends with a clock edge.

and the Moore version gives this one:

Scenario overlap of ch05/s05-detect-moore, 6 cycles, cycle 0 first. Inputs: In is 1, 0, 1, 0, 1, 0. State in each cycle: Idle, One, OneZero, Found, OneZero, Found. Out in each cycle: 0, 0, 0, 1, 0, 1.
Cycle012345
In101010
StateIdleOneOneZeroFoundOneZeroFound
Out000101

Cycle 0 is the first cycle after reset: the machine starts in Idle, and each cycle ends with a clock edge.

Why the one-cycle difference? In the Mealy machine the last 1 of the sequence is on the input in the same cycle that the output rises; the machine is in OneZero. The Moore machine has to move to Found first, and only then does the output rise, so it needs the next clock edge.

The waveforms of two testbenches show the same thing at a finer scale. Both testbenches give the same input. A 1 and then a 0 bring the machine to OneZero. During the cycle in which it is there, In is 0 for the first quarter of the cycle, 1 for the second quarter, 0 for the third and 1 for the last, so it is 1 at the edge that ends the cycle.

Waveform ch05/detect-mealy-mid: clk, reset, In, dut.ps, OutclkresetIndut.psOneOneZeroOneOneZeroOut150175200225250275300325350375400425450475time (ps)Waveform ch05/detect-mealy-mid: clk, reset, In, dut.ps, OutclkresetIndut.psOneOneZeroOneOneZeroOut150175200225250275300325350375400425450475time (ps)Waveform ch05/detect-mealy-mid: clk, reset, In, dut.ps, OutclkresetIndut.psOneOneZeroOneOneZeroOut150200250300350400450time (ps)Waveform ch05/detect-mealy-mid: clk, reset, In, dut.ps, OutclkresetIndut.psOneOneZeroOneOneZeroOut150200250300350400450time (ps)

Time in ps; one column is 25 ps.

Timing diagram from simulation, ch05/detect-mealy-mid: time in ps from 150 to 500, 14 columns of 25 ps.

clk: clock, period 100 ps, starts 1, 3 rising edges in the window (250, 350, 450).

reset: 0 at 150, no changes.

In: 0 at 150, then 1 at 275, 0 at 300, 1 at 325, 0 at 350.

dut.ps (state names: Idle, One, OneZero): One at 150, then OneZero at 250, One at 350, OneZero at 450.

Out: 0 at 150, then 1 at 275, 0 at 300, 1 at 325, 0 at 350.

Dashed vertical guides mark the rising clock edges.

Description: A timing diagram of clk, reset, In, the state dut.ps and Out. Dashed lines mark three rising edges of clk. The state is One until the first dashed line, OneZero from the first to the second, One from the second to the third, and OneZero again after that. In is low at the left, rises a quarter of the way into the OneZero cycle, falls a quarter of a cycle later, rises again a quarter of a cycle after that, and falls at the second dashed line. Out is low except while In is high in the OneZero cycle, so it rises and falls with In twice, and its second high interval ends at the second dashed line.

Figure 5.5.5. The Mealy version: during the cycle in which the machine is in OneZero, Out follows In.
Waveform ch05/detect-moore-mid: clk, reset, In, dut.ps, OutclkresetIndut.psOneOneZeroFoundOneZeroOut150175200225250275300325350375400425450475time (ps)Waveform ch05/detect-moore-mid: clk, reset, In, dut.ps, OutclkresetIndut.psOneOneZeroFoundOneZeroOut150175200225250275300325350375400425450475time (ps)Waveform ch05/detect-moore-mid: clk, reset, In, dut.ps, OutclkresetIndut.psOneOneZeroFoundOneZeroOut150200250300350400450time (ps)Waveform ch05/detect-moore-mid: clk, reset, In, dut.ps, OutclkresetIndut.psOneOneZeroFoundOneZeroOut150200250300350400450time (ps)

Time in ps; one column is 25 ps.

Timing diagram from simulation, ch05/detect-moore-mid: time in ps from 150 to 500, 14 columns of 25 ps.

clk: clock, period 100 ps, starts 1, 3 rising edges in the window (250, 350, 450).

reset: 0 at 150, no changes.

In: 0 at 150, then 1 at 275, 0 at 300, 1 at 325, 0 at 350.

dut.ps (state names: Idle, One, OneZero, Found): One at 150, then OneZero at 250, Found at 350, OneZero at 450.

Out: 0 at 150, then 1 at 350, 0 at 450.

Dashed vertical guides mark the rising clock edges.

Description: A timing diagram of clk, reset, In, the state dut.ps and Out. Dashed lines mark three rising edges of clk. The state is One until the first dashed line, OneZero from the first to the second, Found from the second to the third, and OneZero again after that. The input changes as in the previous figure. Out stays low until the second dashed line, is high for the whole cycle in which the state is Found, and falls at the third dashed line.

Figure 5.5.6. The Moore version, with the same input: Out is 1 for the whole cycle in which the machine is in Found, one cycle after the last 1 of the sequence.

In the second figure Out ignores the changes of In in the OneZero cycle: it depends only on ps, and ps changes only at a clock edge. In the first, Out changes whenever In does while the machine is in OneZero, between edges. Its first high interval is over before the edge, so the register never sees it: ps is loaded from the value of In at the edge, and only the second interval reaches it. A flip-flop placed after the Mealy output would delay it by a cycle, and here that gives exactly the Moore version's output once the reset has cleared it: the two differ in when the output appears, not in what it says.

The case in the next-state logic is inside an always_comb, and the rule for combinational logic is that every path must assign every output. If some value of ps has no arm and nothing assigned ns before the case, then for that value ns keeps its old value. A circuit that keeps an old value is a latch, which is memory that nobody asked for.

The state register has two bits when three states need codes, so it can hold four codes, and one of them is unused. Here is the long-press detector of Section 5.2 with its default arm left out:

latch_demo.sv
// A long-press detector whose next-state logic forgets the default arm: the unused state code 3 has no assignment.
module latch_demo(input logic clk, reset, In, output logic Out);
enum logic [1:0] {S0 = 2'b00, S1 = 2'b01, S2 = 2'b10} ps, ns;
always_comb
case (ps)
S0: if (In) ns = S1;
else ns = S0;
S1: if (In) ns = S2;
else ns = S0;
S2: ns = S0;
endcase
assign Out = (ps == S2) & In;
always_ff @(posedge clk)
if (reset) ps <= S0;
else ps <= ns;
endmodule
Compiles, but is rejected. This file compiles and slang only prints a warning (case-default). The book treats a warning as a failure, so the checks reject it; the warning is shown below the listing.

This is a real file, and the text below is what the book's checking tool printed for it. The tool you use may word its messages differently.

hdl/ch05/s05/ex/latch_demo.sv:6:5: warning: 'case' missing 'default' label [-Wcase-default]
    case (ps)
    ^~~~
hdl/ch05/s05/ex/latch_demo.sv:7:19: warning: latch inferred for 'ns' because it is not assigned on all control paths [-Winferred-latch]
      S0: if (In) ns = S1;
                  ^~
Output of slang for latch_demo

The module is legal SystemVerilog, and the simulator the book uses compiles it without complaint, so a design with this bug can look fine in simulation. The fix is one line: a default arm that assigns ns, as in Section 5.2, or an assignment to ns before the case so that no path is left out. Apart from its name, the repaired file differs from the broken one only in that arm, and it passes every check.

latch_fixed.sv
// The module of latch_demo with its next state assigned on every path: the default arm covers the unused code.
module latch_fixed(input logic clk, reset, In, output logic Out);
enum logic [1:0] {S0 = 2'b00, S1 = 2'b01, S2 = 2'b10} ps, ns;
always_comb
case (ps)
S0: if (In) ns = S1;
else ns = S0;
S1: if (In) ns = S2;
else ns = S0;
S2: ns = S0;
default: ns = S0;
endcase
assign Out = (ps == S2) & In;
always_ff @(posedge clk)
if (reset) ps <= S0;
else ps <= ns;
endmodule

A shorter way to write next state uses a conditional expression, and the Section 5 slides show it for next-state logic as shorthand for if and else (fact ch05/ternary-next-state): ns = In ? S1 : S0; means "S1 if In is 1, otherwise S0". It is the if and else of one arrow pair, squeezed into one line. Here is the long-press next-state logic in that style:

long_press_tern.sv
always_comb
case (ps)
S0: ns = In ? S1 : S0;
S1: ns = In ? S2 : S0;
S2: ns = S0;
default: ns = S0;
endcase
Not simulated. Yosys proved this module equivalent to ch05/long_press.

The line says the same as the if/else version in Section 5.2, and it is legal SystemVerilog: the book's slang, Verilator and Yosys checks accept it. The one tool that rejects it is Icarus, the simulator this book uses: it stops with an error about an assignment that needs an explicit cast, because the conditional picks between two enum values and assigns the result to an enum. Rather than leave the idiom untested, the book puts this module in a no-simulation tier: it is never simulated, and the synthesis tool Yosys, using a formal equivalence check, proves that its logic equals that of the simulated if/else module of Section 5.2. The rest of the book writes next state with case and if/else only because of that limit of its simulator. In your own code either form works; use the one your course section uses.

How CSE 369 writes this

In CSE 369 we write every state machine in the four parts above, with ps for the present state and ns for the next state, a synchronous reset, = inside the combinational blocks and <= inside always_ff. The state variable is driven from only one block, and a plain always is used only in a testbench. The course teaches Mealy machines first, so an output that reads an input is the normal case and the output of a Moore machine, written inside the circle in a diagram, is the exception.

Common pitfall

  • Leaving out the default arm. When a case does not cover every code of the state register, the next state is held for the codes that are left out, and lint and synthesis tools report a latch (a simulator may not).
  • Mixing the assignment styles. = inside always_ff makes the result depend on the order of the statements (Section 4.8 shows two flip-flops becoming one), and <= inside always_comb can make the block read stale values. Some tools flag both.
  • Forgetting to declare ns, or writing the next state straight into ps. The register input and the register output are two different signals.
  • Expecting a Mealy output to wait for the clock edge. Any output that reads an input changes when the input does.
  • Naming the states in the code differently from the diagram, or giving the enum fewer bits than its codes need.
Take the quiz (5 questions)

Which of the four parts of the pattern is the only one that holds a value from one clock cycle to the next?

A module computes its output with assign Out = (ps == Done) & Start;. What kind of machine is it?

For the input 1, 0, 1, 0, 1, 0 in cycles 0 to 5 of a scenario table (cycle 0 is the first cycle after reset), what output does the Mealy version of the 1, 0, 1 detector give?

For the same input, what does the Moore version of the detector give?

A next-state case handles three states of a two-bit register and has no default. What does a lint or synthesis tool report?

Exercise 5.5.1

A red-light, green-light game has two inputs: L is 1 while the light is green, and M is 1 while the player moves. A move while the light is green is one successful step, and three successful steps in a row win. The output W is 1 in the cycle of the third step, and then the game starts over. Moving while the light is red sends the player back to the start. Standing still, with M at 0, keeps the count whatever the light shows. Draw the state diagram and write the module in the four parts.

Hint
The state is how many successful steps there are so far: 0, 1 or 2. What does each of the four combinations of L and M do in each state? A condition such as L & M picks the arrow for a successful step, and M alone then picks the arrow for a move on red.
Solution

Three counts are worth remembering, so there are three states, which we call Start, Once and Twice. In every state a step on green (L and M both 1) advances the count, and from Twice it wins: the output is 1 and the machine goes back to Start. A move on red (M is 1 and L is 0) returns to Start with output 0. Standing still (M is 0) keeps the state, with output 0.

State diagram of s05-light-gameState diagram of s05-light-game: a Mealy machine with 3 states (Start, Once, Twice). The initial state is Start, marked by an arrow that comes from no state. From Start when L=1, M=1, output 0, go to Once. From Start when L=0, M=1, output 0, go to Start. From Start when M=0, output 0, go to Start. From Once when L=1, M=1, output 0, go to Twice. From Once when L=0, M=1, output 0, go to Start. From Once when M=0, output 0, go to Once. From Twice when L=1, M=1, output 1, go to Start. From Twice when L=0, M=1, output 0, go to Start. From Twice when M=0, output 0, go to Twice. Inputs, in label order: L, M. Outputs: W.LM/0LM+M/0LM/0LM/0M/0LM/1LM/0M/0StartOnceTwicestart

Description: State diagram with three circles Start, Once and Twice in a row. A starting arrow enters Start. An arrow for a move on green goes from Start to Once and from Once to Twice with output 0, and an arrow with output 1 for the same input goes from Twice back to Start. A move on red returns Once and Twice to Start with output 0. Each state has a self-loop for the cases in which the state does not change.

Figure 5.5.7. The game machine. The arrows that stay in a state are drawn once, for the cases in which the player does not move or, in Start, moves on red.

The module follows the diagram arm by arm. The Start arm has one arrow out, for L and M together. The Once arm has two arrows out, an if for a step on green and an else if for a move on red. The Twice arm has only one test, M, because both ways of moving, on green or on red, end in Start; only the output tells them apart. The default arm covers the unused code.

light_game.sv
// A red-light, green-light game. L is 1 while the light is green, M is 1 while the player moves.
// Three green-light moves in a row win; W is 1 in the cycle of the third one.
module light_game(input logic clk, reset, L, M, output logic W);
enum logic [1:0] {Start = 2'b00, Once = 2'b01, Twice = 2'b10} ps, ns;
always_comb
case (ps)
Start: if (L & M) ns = Once;
else ns = Start;
Once: if (L & M) ns = Twice;
else if (M) ns = Start;
else ns = Once;
Twice: if (M) ns = Start;
else ns = Twice;
default: ns = Start;
endcase
assign W = (ps == Twice) & L & M;
always_ff @(posedge clk)
if (reset) ps <= Start;
else ps <= ns;
endmodule

Tags: StandardFinite state machinesSystemVerilog

Exercise 5.5.2

The Moore version of the detector, shown above, is given the input 1, 1, 1, 0, 1, 0 in cycles 0 to 5, where cycle 0 is the first cycle after reset, as in a scenario table. Fill in the state in each cycle and the output.

Scenario ones-then-pattern of ch05/s05-detect-moore, 6 cycles, cycle 0 first. Inputs: In is 1, 1, 1, 0, 1, 0. The state and output rows are empty for the reader to fill in.
Cycle012345
In111010
Stateblankblankblankblankblankblank
Outblankblankblankblankblankblank

Cycle 0 is the first cycle after reset: the machine starts in Idle, and each cycle ends with a clock edge.

Hint
The output is 1 in any cycle in which the machine is in Found. In which cycle does the machine first reach it?
Solution

Take the input in the order it arrives. In cycle 0 the machine is in Idle and the input is 1, so the next state is One. In cycles 1 and 2 it is in One with a 1 on the input, so it stays there. In cycle 3 the input is 0, so the next state is OneZero. In cycle 4 the machine is in OneZero and the input is 1, which completes the sequence, so the next state is Found. In cycle 5 it is in Found, and the output is 1; the input is 0, so the next state is OneZero. The output is 1 only in cycle 5, the cycle after the one with the last 1 of the sequence.

Scenario ones-then-pattern of ch05/s05-detect-moore, 6 cycles, cycle 0 first. Inputs: In is 1, 1, 1, 0, 1, 0. State in each cycle: Idle, One, One, One, OneZero, Found. Out in each cycle: 0, 0, 0, 0, 0, 1.
Cycle012345
In111010
StateIdleOneOneOneOneZeroFound
Out000001

Cycle 0 is the first cycle after reset: the machine starts in Idle, and each cycle ends with a clock edge.

Tags: Warm-upFinite state machinesSequential logic

Exercise 5.5.3

The Mealy detector for the sequence 1, 0, 1 is run with an input that moves in the middle of clock cycles. The figure shows the clock, reset and In for a stretch of the run. Just before the first rising edge shown, at 250, the machine is in Idle. In this simulation In never changes at an edge. Work out the state dut.ps and the output Out over the whole stretch, including the moments between edges.

Waveform ch05/detect-mealy-q: clk, reset, In, dut.ps, OutclkresetIndut.psOut225250275300325350375400425450475500525550575time (ps)Waveform ch05/detect-mealy-q: clk, reset, In, dut.ps, OutclkresetIndut.psOut225250275300325350375400425450475500525550575time (ps)Waveform ch05/detect-mealy-q: clk, reset, In, dut.ps, OutclkresetIndut.psOut225275325375425475525575time (ps)Waveform ch05/detect-mealy-q: clk, reset, In, dut.ps, OutclkresetIndut.psOut225275325375425475525575time (ps)Waveform ch05/detect-mealy-q: clk, reset, In, dut.ps, OutclkresetIndut.psOut225275325375425475525575time (ps)

Time in ps; one column is 25 ps.

Timing diagram from simulation, ch05/detect-mealy-q: time in ps from 225 to 600, 15 columns of 25 ps.

clk: clock, period 100 ps, starts 0, 4 rising edges in the window (250, 350, 450, ...).

reset: 0 at 225, no changes.

In: 1 at 225, then 0 at 275, 1 at 375, 0 at 400, 1 at 425, 0 at 475, 1 at 575.

Empty lanes for the reader to complete: dut.ps, Out.

Dashed vertical guides mark the rising clock edges.

Description: A timing diagram of clk, reset and In, with lanes for dut.ps and Out left empty. reset is low throughout. The clock is low at the left edge of the figure and rises at 250, with In high; In falls at 275, a quarter of the way into the first cycle. In the second cycle, from 350 to 450, it is high from 375 to 400 and high again from 425, and it stays high through the rising edge at 450 until 475. It then stays low until 575, when it rises and is high at the end of the figure.

Figure 5.5.8. The question: the clock, reset and In.
Hint
The state changes only at a rising edge, to the state the arrow for the value In has at that edge leads to. The output is not registered: while the machine is in OneZero, Out equals In, and in every other state it is 0, so Out follows In between the edges.
Solution

Take the edges one at a time, using the value In has at each edge, and then fill in Out between the edges from the state and In.

  • Edge at 250: In is 1 and the machine was in Idle, so it goes to One. Until the next edge Out is 0, whatever In does, because only OneZero raises it. In falls at 275.
  • Edge at 350: In is 0 in One, so the machine goes to OneZero.
  • In the cycle from 350 to 450 the machine is in OneZero, so Out equals In. In rises at 375 and falls at 400, so Out is high from 375 to 400. In rises again at 425, so Out is high from 425 until the next edge.
  • Edge at 450: In is 1 in OneZero, so the machine goes to One, where Out is 0. Out falls at 450, although In is still 1. In falls at 475.
  • Edge at 550: In is 0 in One, so the machine goes to OneZero. In rises at 575 and Out rises with it, so Out is high from 575 to the end of the figure, which is before the next edge.
Waveform ch05/detect-mealy-q: clk, reset, In, dut.ps, OutclkresetIndut.psIdleOneOneZeroOneOneZeroOut225250275300325350375400425450475500525550575time (ps)Waveform ch05/detect-mealy-q: clk, reset, In, dut.ps, OutclkresetIndut.psIdleOneOneZeroOneOneZeroOut225250275300325350375400425450475500525550575time (ps)Waveform ch05/detect-mealy-q: clk, reset, In, dut.ps, OutclkresetIndut.psIdleOneOneZeroOneOneZeroOut225275325375425475525575time (ps)

Time in ps; one column is 25 ps.

Timing diagram from simulation, ch05/detect-mealy-q: time in ps from 225 to 600, 15 columns of 25 ps.

clk: clock, period 100 ps, starts 0, 4 rising edges in the window (250, 350, 450, ...).

reset: 0 at 225, no changes.

In: 1 at 225, then 0 at 275, 1 at 375, 0 at 400, 1 at 425, 0 at 475, 1 at 575.

dut.ps (state names: Idle, One, OneZero): Idle at 225, then One at 250, OneZero at 350, One at 450, OneZero at 550.

Out: 0 at 225, then 1 at 375, 0 at 400, 1 at 425, 0 at 450, 1 at 575.

Dashed vertical guides mark the rising clock edges.

Description: The full timing diagram of clk, reset, In, dut.ps and Out. The state is Idle until 250, One from 250 to 350, OneZero from 350 to 450, One from 450 to 550 and OneZero from 550. Out is high from 375 to 400, from 425 to 450, and from 575.

Figure 5.5.9. The answer, from the simulation of the testbench listed below.

The stimulus that produced the figure. Each // expect: comment on a line that ends with a delay is read just before that delay ends, in the middle of a cycle.

detect_mealy_q_tb.sv
// Exercise: the stimulus for a Mealy trace question. In changes in the middle of cycles, a quarter of a period after an edge or later, never at an edge.
module detect_mealy_q_tb();
logic clk, reset, In, Out;
detect_mealy dut(.*);
parameter CLOCK_PERIOD = 100;
initial begin
clk <= 0;
forever #(CLOCK_PERIOD / 2) clk <= ~clk;
end
initial begin
reset <= 1; In <= 0; @(posedge clk);
#(CLOCK_PERIOD / 4); // expect: ps=Idle, Out=0
reset <= 0; In <= 1; #(CLOCK_PERIOD / 2); // expect: ps=Idle, Out=0
In <= 0; @(posedge clk); // expect: ps=Idle, Out=0
#(CLOCK_PERIOD / 4); // expect: ps=Idle, Out=0
In <= 1; @(posedge clk); // expect: ps=Idle, Out=0
// The edge just passed saw In = 1 in Idle, so the machine is in One.
#(CLOCK_PERIOD / 4); // expect: ps=One, Out=0
In <= 0; @(posedge clk); // expect: ps=One, Out=0
// The edge saw In = 0 in One, so the machine is in OneZero, and In now moves inside the cycle.
#(CLOCK_PERIOD / 4); // expect: ps=OneZero, Out=0
In <= 1; #(CLOCK_PERIOD / 4); // expect: ps=OneZero, Out=1
In <= 0; #(CLOCK_PERIOD / 4); // expect: ps=OneZero, Out=0
In <= 1; @(posedge clk); // expect: ps=OneZero, Out=1
// In was 1 at the edge, so the machine is in One again, with In still 1 for a while.
#(CLOCK_PERIOD / 4); // expect: ps=One, Out=0
In <= 0; @(posedge clk); // expect: ps=One, Out=0
#(CLOCK_PERIOD / 4); // expect: ps=OneZero, Out=0
In <= 1; #(CLOCK_PERIOD / 4); // expect: ps=OneZero, Out=1
In <= 0; @(posedge clk); // expect: ps=OneZero, Out=0
@(posedge clk); // expect: ps=Idle, Out=0
$stop;
end
endmodule

Tags: StandardFinite state machinesWaveforms

Exercise 5.5.4

This next-state logic reports a latch. Find the missing piece and say how you would fix it in two different ways.

latch_demo.sv
// A long-press detector whose next-state logic forgets the default arm: the unused state code 3 has no assignment.
module latch_demo(input logic clk, reset, In, output logic Out);
enum logic [1:0] {S0 = 2'b00, S1 = 2'b01, S2 = 2'b10} ps, ns;
always_comb
case (ps)
S0: if (In) ns = S1;
else ns = S0;
S1: if (In) ns = S2;
else ns = S0;
S2: ns = S0;
endcase
assign Out = (ps == S2) & In;
always_ff @(posedge clk)
if (reset) ps <= S0;
else ps <= ns;
endmodule
Compiles, but is rejected. This file compiles and slang only prints a warning (case-default). The book treats a warning as a failure, so the checks reject it; the warning is shown below the listing.
Hint
For which value of ps is ns not assigned?
Solution

The register has two bits, so ps can hold the code 11, and no arm of the case handles it. The first fix is a default arm that assigns ns, which is what the module of Section 5.2 does: the machine goes to S0 from any code that is not a state. The second fix is to give ns a value before the case, for example ns = S0; on the line before it, so that every path assigns ns and the arms only override it. That puts two statements in the always_comb, the default assignment and the case, so the body needs begin and end around it. Without them the block would be only ns = S0;, and the case would stand outside any block, which is a syntax error. Both versions are free of latches; the first is the form the book uses.

latch_fixed.sv
// The module of latch_demo with its next state assigned on every path: the default arm covers the unused code.
module latch_fixed(input logic clk, reset, In, output logic Out);
enum logic [1:0] {S0 = 2'b00, S1 = 2'b01, S2 = 2'b10} ps, ns;
always_comb
case (ps)
S0: if (In) ns = S1;
else ns = S0;
S1: if (In) ns = S2;
else ns = S0;
S2: ns = S0;
default: ns = S0;
endcase
assign Out = (ps == S2) & In;
always_ff @(posedge clk)
if (reset) ps <= S0;
else ps <= ns;
endmodule

Here is the second version as a file, with its begin and end. It has no latch, and it is correct. The book still prefers the first form, for a design reason: the default arm says, next to the arms, what happens for a code that no arm names, and the case stays fully assigned even if someone later deletes the first line of the block. The checker's warning about a case with no default is a second point, a tool noticing the same thing, and the book treats every warning as a failure.

latch_fixed_pre.sv
// The module of latch_demo with a default value for ns assigned before the case, so that no path leaves ns unassigned.
module latch_fixed_pre(input logic clk, reset, In, output logic Out);
enum logic [1:0] {S0 = 2'b00, S1 = 2'b01, S2 = 2'b10} ps, ns;
always_comb begin
ns = S0;
case (ps)
S0: if (In) ns = S1;
S1: if (In) ns = S2;
S2: ns = S0;
endcase
end
assign Out = (ps == S2) & In;
always_ff @(posedge clk)
if (reset) ps <= S0;
else ps <= ns;
endmodule
Compiles, but is rejected. This file compiles and slang only prints a warning (case-default). The book treats a warning as a failure, so the checks reject it; the warning is shown below the listing.
hdl/ch05/s05/ex/latch_fixed_pre.sv:7:5: warning: 'case' missing 'default' label [-Wcase-default]
    case (ps)
    ^~~~
Output of slang for latch_fixed_pre

Tags: StandardSystemVerilogSequential logic

Exercise 5.5.5

Could a Moore machine raise Out in the same cycle that the last 1 of the sequence is on In, as the Mealy version does? Explain why or why not.

Hint
A Moore output depends only on the state. What does the state know about In during the cycle in which In has just changed?
Solution

No. During a cycle the state holds what happened up to the previous clock edge, so it cannot depend on the value In takes in the current cycle. An output that reads only the state can therefore not react to the input of the same cycle. A Moore machine can raise the output one cycle later, as the Moore version above does, but it cannot produce the Mealy output exactly. Choosing between the two is a choice of when the output is allowed to change.

Tags: ChallengeFinite state machinesSequential logic