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:
- 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.
- 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:
Best Regards,
– Tamim