State machines#

Learning goals#

  • Apply basic features of the language to describe state machines

Introductory problem#

Implement a traffic light controller with the following requirements:

  • The lights cycle through the following sequence:

    1. 🔴 three time-units

    2. 🟡 one time-unit

    3. 🟢 three time-units

    4. back to 1.

  • Must have the following interface:

module traffic_light_controller #(
    int unsigned RED_DURATION = 3,
    int unsigned YELLOW_DURATION = 1,
    int unsigned GREEN_DURATION = 3
) (
    input  clk,
    rst,
    output red,
    yellow,
    green
);

Implementation on the board#

  • Use the RGB LED RGB0 as output.

  • Use a time-unit of one second.

Tasks#

Read section 6 of SV Guide.

Quiz#

TODO

Mini-lecture#

Enumerations#

Moore and Mealy FSM#

systemverilog.dev/4.html => 4.2.1

One-, two-, and three-block FSM#

systemverilog.dev/4.html => 4.2.4

Solution for the introductory problem#

Listing 17 code/traffic_light_controller/traffic_light_controller.sv#
module traffic_light_controller #(
    int unsigned RED_DURATION = 3,
    int unsigned YELLOW_DURATION = 1,
    int unsigned GREEN_DURATION = 3
) (
    input  clk,
    rst,
    output red,
    yellow,
    green
);
  function automatic int unsigned max_of_three(int unsigned a, int unsigned b, int unsigned c);
    return a >= b ? ((a >= c) ? a : c) : b >= c ? b : c;
  endfunction

  localparam int unsigned MaxDuration = max_of_three(RED_DURATION, YELLOW_DURATION, GREEN_DURATION);
  typedef enum {
    SET_TIMER_FOR_RED,
    RED,
    YELLOW,
    GREEN
  } st_t;
  st_t st, stn;
  logic [$clog2(MaxDuration) -1 : 0] delay_cntr, state_start_timestamp, state_start_timestampn;

  always_ff @(posedge clk or posedge rst) begin
    st <= rst ? st.first : stn;
    delay_cntr <= rst ? 0 : delay_cntr + 1;
    state_start_timestamp <= rst ? 0 : state_start_timestampn;
  end

  always_comb begin
    stn = st;
    state_start_timestampn = state_start_timestamp;
    unique case (st)
      // Assume that the clock has the frequency of the minimum
      // duration – in our case 1.
      SET_TIMER_FOR_RED: begin
        state_start_timestampn = delay_cntr;
        stn = st.next;
      end
      RED:
      if (delay_cntr == state_start_timestamp + $bits(delay_cntr)'(RED_DURATION)) begin
        stn = st.next;
        state_start_timestampn = delay_cntr;
      end
      YELLOW:
      if (delay_cntr == state_start_timestamp + $bits(delay_cntr)'(YELLOW_DURATION)) begin
        stn = st.next;
        state_start_timestampn = delay_cntr;
      end
      GREEN:
      if (delay_cntr == state_start_timestamp + $bits(delay_cntr)'(GREEN_DURATION)) begin
        stn = RED;
        state_start_timestampn = delay_cntr;
      end
    endcase
  end

  assign red = (st == RED) ? 1 : 0;
  assign yellow = (st == YELLOW) ? 1 : 0;
  assign green = (st == GREEN) ? 1 : 0;
endmodule

Warning

During implementation I made the following mistake by writing

state_start_timestamp <= state_start_timestampn;

instead of

state_start_timestamp <= rst ? 0 : state_start_timestampn;

in the sequential block, which led to the following cryptic error in Vivado:

ERROR: [Synth 8-91] ambiguous clock in event control ...

If we don’t use rst in our statement above, then this statement must be executed in both of posedge clk and posedge rst events. Synthesizers typically infer a flip-flop if they see that a variable is updated on the positive or negative edge of a signal. However there are typically no flip-flops that are updated using two separate clock signals. That is where the ambiguity comes from. Now the error this not cryptic anymore, at least to me.

Listing 18 code/traffic_light_controller/tb.sv#
module tb;
  logic clk = 0, rst = 0;
  logic red, yellow, green;
  always #1 clk <= ~clk;

  traffic_light_controller dut (.*);
  initial begin
    rst = 0;
    #2 rst = 1;
    #2 rst = 0;
    wait (dut.st == dut.RED);
    assert (red);
    repeat (dut.RED_DURATION) wait (clk == 0) wait (clk == 1);

    #1 assert (dut.st == dut.YELLOW);  // #1 causes to sample after the signals have settled.
    assert (yellow);
    repeat (dut.YELLOW_DURATION) wait (clk == 0) wait (clk == 1);

    #1 assert (dut.st == dut.GREEN);
    assert (green);
    repeat (dut.GREEN_DURATION) wait (clk == 0) wait (clk == 1);

    #1 assert (dut.st == dut.RED);
    assert (red);

    #10 $finish;
  end

  initial begin
    $dumpfile("signals.fst");
    $dumpvars;
  end
endmodule
Listing 19 code/traffic_light_controller_boolean/traffic_light_controller_boolean.sv#
module traffic_light_controller_boolean #(
    int unsigned RED_DURATION = 3,
    int unsigned YELLOW_DURATION = 1,
    int unsigned GREEN_DURATION = 3,
    int unsigned CLKDIV_FOR_1S = 100e6  // 1 s
) (
    input clk,
    input [3:0] btn,
    output [2:0] RGB0
);
  logic clk_1s, rst, red, yellow, green;
  clkdiv #(CLKDIV_FOR_1S) clkdiv_1s (
      .i(clk),
      .o(clk_1s)
  );

  traffic_light_controller #(
      .RED_DURATION(RED_DURATION),
      .YELLOW_DURATION(YELLOW_DURATION),
      .GREEN_DURATION(GREEN_DURATION)
  ) tlc_i (
      .clk(clk_1s),
      .*
  );

  assign RGB0 = {1'b0, green | yellow, red | yellow};
  assign rst  = |btn;
endmodule

Homework#

Exercise 23

Augment the traffic light controller design above so that an ambulance can cross in an emergency case.

Requirements:

  1. Augment the interface with the input emergency. If this input is active, then the traffic light controller leaves immediately 🔴 state.

  2. Draw a state diagram

  3. Your testbench must also verify the requirement 1

Exercise 24

Implement the previous problem using one-block Mealy, two-block Mealy, and three-block Moore FSM.