Implementing a State Machine using a SystemVerilog Class

I am performing an experiment implementing a simple state machine in a SystemVerilog Class. I am interested in doing it this way because my real task is interfacing a BFM to an interface, and it seemed natural to me to run the interface code using a Class Task (so I can run it on a clock).

This experiment is a simple state machine that simply finds three consecutive ones in a single-bit data stream. When it finds three consecutive bits, it asserts an output called “found_three”.

First, here is the interface (sm_signal_if.sv):


`ifndef __SM_SIGNAL_IF__
`define __SM_SIGNAL_IF__

interface sm_signal_if (
   input logic clk,
   input logic rst_n,
   input logic data
);
   // Data and Output
   logic found_three;
	
   modport master (
        input  found_three, clk, rst_n, data
   );
   
   modport slave (
        output found_three, 
        input  data, clk, rst_n
  );

endinterface : sm_signal_if
`endif // __SM_SIGNAL_IF__

Here is the top-level testbench (testbench_sm_class.sv):


`timescale 1 ns/10 ps

module testbench_sm_class;

parameter clk_cycle = 4ns; // 250MHz Clock

logic clk = 1'b0;
logic rst_n = 1'b0;
bit data;
//-------------------------------------
// Instantiation of the Sigs Interface
//-------------------------------------
sm_signal_if sigs(
   .clk(clk),
   .rst_n(rst_n),
   .data(data)
);

//-------------
// Clock Logic
//-------------
initial
begin
    forever #(clk_cycle/2) clk = ~clk;
end

initial
begin
   repeat (100) @(posedge clk);
   #100ps
   rst_n = 1'b1;
   repeat (1000) @(posedge clk);
   $finish;
end

always @(negedge clk)
begin
   if (!rst_n)
      data = 1'b0;
   else
      data = $urandom();
end
//--------------------------------------------
// Instantiation of the test on State Machine 
//--------------------------------------------
test_sm_class_directed directed_test(
    .sigs(sigs)
);

endmodule

Here is the instantiated test in the testbench (test_sm_class_directed.sv):


//---------------------------------------------------------
// Test module for the simulation. 
//   Simple test using a class for the State Machine.
//---------------------------------------------------------
module test_sm_class_directed(
    sm_signal_if sigs 
);

import sm_class_pkg::*;

sm_state_t local_sm, local_next;

//---------------------------------------------------------
StateMachine state_machine;

initial
begin
   state_machine = new(.sigs(sigs));
   fork
      state_machine.run();
   join_none
end

initial
begin
   forever begin
      @(posedge sigs.clk)
      begin
         local_sm = state_machine.sm_state;
         local_next = state_machine.sm_next;
      end
   end
end

endmodule

Here is the class where the state machine finding three consecutive ones in the single-bit “data” stream resides(sm_class_pkg.sv):


`ifndef __SM_CLASS_PKG__
`define __SM_CLASS_PKG__

package sm_class_pkg; 

   typedef enum bit [2:0] {
      RESET,
      READY,
      GOT_ONE,
      GOT_TWO,
      GOT_THREE
   } sm_state_t;

class StateMachine;

   // Data Members
    sm_state_t sm_state, sm_next;

   virtual sm_signal_if sigs;

   // Constructor
   function new(
      virtual sm_signal_if sigs
   );
      this.sigs = sigs;
      this.sm_state = RESET;
      this.set_next_state();
      this.set_outputs();
   endfunction


   protected function void set_next_state();
      if (!sigs.rst_n)
      begin
         sm_next = RESET;
      end
      else
      begin
         case (sm_state)
            RESET: begin
               if (sigs.rst_n)
                  if (sigs.data == 1'b1)
                     sm_next = GOT_ONE;
                  else
                     sm_next = READY;
               else
                  sm_next = RESET;
            end
            READY: begin
               if (sigs.data == 1'b1)
                  sm_next = GOT_ONE;
               else
                  sm_next = READY;
            end
            GOT_ONE: begin
               if (sigs.data == 1'b1)
                  sm_next = GOT_TWO;
               else
                  sm_next = READY;
            end
            GOT_TWO: begin
               if (sigs.data == 1'b1)
                  sm_next = GOT_THREE;
               else
                  sm_next = READY;
            end
            GOT_THREE: begin
               if (sigs.data == 1'b1)
                  sm_next = GOT_THREE;
               else
                  sm_next = READY;
            end
         endcase
      end
   endfunction


   protected function void set_outputs();
      if (sm_state == GOT_THREE)
         sigs.found_three = 1'b1;
      else
         sigs.found_three = 1'b0;
   endfunction


   task run();
      $timeformat(-9, 3, "ns", 4);
      set_next_state();
      forever begin
         @(posedge sigs.clk)
         begin
            set_next_state();
            sm_state = sm_next;
            set_outputs();
         end
      end
   endtask

endclass : StateMachine

endpackage: sm_class_pkg

`endif // __SM_CLASS_PKG__

Now this definitely works (see waveform snippet below), but the coding seems rather amateurish to me and perhaps not optimal since I coded it similar to RTL, which is probably not great. I’m also not sure that the coding in my directed test is good. My concerns:

  1. The StateMachine object is created in an initial block, and a separate initial block is counting on it being there – possibly creating a race condition.
  2. I used local variables in the test to reflect the values of the state state machine object. This also could be a race since the state machine is assigned and evaluated at the same time (on @(posedge sigs.clk)).

Anyway, I’m hoping someone out there has more experience in these matters and can provide some “best practices” and “avoidance-of-pitfalls” advice.

Here is the sample waveform of the state machine in action:

Google Photos

Best Regards,

– Tamim

In reply to DigitalAce:

Ah… it looks like the simulation photo link didn’t work… I’ll see if I can fix it.

In reply to DigitalAce:

Your link is not an image, it is a page containing an image.

You need to use nonblocking assignments to your state variables to avoid the races.

In reply to dave_59:

Thanks for the feedback, Dave! Yeah, I might actually get rid of the next state stuff altogether since I’m not generating synthesizable code. Otherwise making the state assignment nonblocking would be very sensible.

Is there anything else that should be improved? I’d be surprised if I did it relatively right on the first attempt!

In reply to dave_59:

Dave,

Regarding the image, when I clicked on the “Picture” button for the post, it asks for a URL, which is why I provided one.

I’ll try a different link to see if that works: