Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Translating a SystemVerilog FSM to Cycle-Accurate SystemC

Tech Jun 18 1

SystemVerilog Reference Implementation

To illustrate how SystemC can be utilized to model Register Transfer Level (RTL) hardware, we will examine a standard Finite State Machine (FSM) implementation. Below is a SystemVerilog module defining a three-state controller.

module seq_controller (
    input  logic       clk,
    input  logic       rst_n,
    input  logic [1:0] data_in,
    output logic [1:0] status_out
);

    typedef enum logic [1:0] {
        S_IDLE = 2'b00,
        S_BUSY = 2'b01,
        S_DONE = 2'b10
    } state_t;

    state_t current_s, next_s;

    // State Register Update
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            current_s <= S_IDLE;
        else
            current_s <= next_s;
    end

    // Next State Logic
    always_comb begin
        next_s = current_s;
        case (current_s)
            S_IDLE:  if (data_in == 2'b01) next_s = S_BUSY;
            S_BUSY:  if (data_in == 2'b10) next_s = S_DONE;
            S_DONE:  if (data_in == 2'b11) next_s = S_IDLE;
            default: next_s = S_IDLE;
        endcase
    end

    // Output Logic
    always_comb begin
        status_out = 2'b00;
        case (current_s)
            S_IDLE:  status_out = 2'b01;
            S_BUSY:  status_out = 2'b10;
            S_DONE:  status_out = 2'b11;
            default: status_out = 2'b00;
        endcase
    end

endmodule

SystemC Implementation

The following code demonstrates the equivalent functionality in SystemC. This model uses processes registered with SC_METHOD to mimic the behavior of the SystemVerilog always blocks.

Header File (seq_ctrl.h)

#ifndef SEQ_CTRL_H_
#define SEQ_CTRL_H_

#include <systemc.h>

SC_MODULE(SeqController) {
    sc_in<bool> clk;
    sc_in<bool> rst_n;
    sc_in<sc_uint<2>> data_in;
    sc_out<sc_uint<2>> status_out;

    sc_signal<sc_uint<2>> cs; // Current State
    sc_signal<sc_uint<2>> ns; // Next State

    void update_state();
    void transition_logic();
    void output_logic();
    void monitor_trace();

    SC_CTOR(SeqController) {
        SC_METHOD(update_state);
        sensitive << clk.pos() << rst_n.neg();

        SC_METHOD(transition_logic);
        sensitive << data_in << cs;

        SC_METHOD(output_logic);
        sensitive << cs;

        SC_METHOD(monitor_trace);
        sensitive << clk.pos();
    }

    enum StateType {
        IDLE = 0,
        BUSY = 1,
        DONE = 2
    };
};

#endif

Implementation File (seq_ctrl.cpp)

#include "seq_ctrl.h"
#include <iostream>

void SeqController::update_state() {
    if (rst_n.read() == false) {
        cs.write(IDLE);
    } else {
        cs.write(ns.read());
    }
}

void SeqController::transition_logic() {
    switch (cs.read()) {
        case IDLE:
            if (data_in.read() == 1) ns.write(BUSY);
            else ns.write(cs.read());
            break;
        case BUSY:
            if (data_in.read() == 2) ns.write(DONE);
            else ns.write(cs.read());
            break;
        case DONE:
            if (data_in.read() == 3) ns.write(IDLE);
            else ns.write(cs.read());
            break;
        default:
            ns.write(IDLE);
            break;
    }
}

void SeqController::output_logic() {
    switch (cs.read()) {
        case IDLE:  status_out.write(1); break;
        case BUSY:  status_out.write(2); break;
        case DONE:  status_out.write(3); break;
        default:    status_out.write(0); break;
    }
}

void SeqController::monitor_trace() {
    std::cout << sc_time_stamp() << " | In: " << data_in.read()
              << " | State: " << cs.read()
              << " | Next: " << ns.read()
              << " | Out: " << status_out.read() << std::endl;
}

Testbench and Simulation

The testbanch below instantiates the module, applies the stimulus, and generates a VCD trace file for waveform analysis.

#include "seq_ctrl.h"

int sc_main(int argc, char* argv[]) {
    sc_clock sys_clk("sys_clk", 2, SC_NS);
    sc_signal<bool> rst;
    sc_signal<sc_uint<2>> input_sig;
    sc_signal<sc_uint<2>> output_sig;

    // Instantiate Module
    SeqController dut("dut");
    dut.clk(sys_clk);
    dut.rst_n(rst);
    dut.data_in(input_sig);
    dut.status_out(output_sig);

    // Waveform Tracing
    sc_trace_file* wf = sc_create_vcd_trace_file("waveform_dump");
    sc_trace(wf, sys_clk, "clock");
    sc_trace(wf, rst, "reset");
    sc_trace(wf, input_sig, "input");
    sc_trace(wf, output_sig, "output");
    sc_trace(wf, dut.cs, "current_state");
    sc_trace(wf, dut.ns, "next_state");

    // Stimulus Process
    rst.write(0);
    input_sig.write(0);
    sc_start(1, SC_NS);

    rst.write(1);
    sc_start(3, SC_NS);

    input_sig.write(1); // Trigger IDLE -> BUSY
    sc_start(2, SC_NS);

    input_sig.write(2); // Trigger BUSY -> DONE
    sc_start(2, SC_NS);

    input_sig.write(3); // Trigger DONE -> IDLE
    sc_start(5, SC_NS);

    sc_close_vcd_trace_file(wf);
    return 0;
}

Simulation Output

0 s | In: 0 | State: 0 | Next: 0 | Out: 1
2 ns | In: 0 | State: 0 | Next: 0 | Out: 1
4 ns | In: 1 | State: 0 | Next: 1 | Out: 1
6 ns | In: 2 | State: 1 | Next: 2 | Out: 2
8 ns | In: 3 | State: 2 | Next: 0 | Out: 3
10 ns | In: 3 | State: 0 | Next: 0 | Out: 1

Analysis and Design Considerations

By comparing the two implementations, we can establish a direct mapping between SystemVerilog and SystemC constructs:

  • Ports: input/output in SystemVerilog correspond to sc_in/sc_out in SystemC.
  • Signals: Internal wires and regs map to sc_signal.
  • Processes: always_ff and always_comb blocks are implemented using SC_METHOD processes, which execute whenever their sensitivity list changes.
  • Instantiation: Module instantiation corresponds to the construction of a C++ object and binding its ports to signals.

While SystemC is fully capable of supporting cycle-accurate, clock-driven models as shown above, its primary advantage lies in Transaction-Level Modeling (TLM). In a standard top-down design flow, engineers often start with loosely timed or untimed models to rapidlly verify architectural functionality and communication protocols without the overhead of clock-cycle details. This allows the SystemC model to serve as an executable specification for software developers and as a reference for RTL implementation teams. The strict cycle-accurate modeling shown here is typically reserved for later stages when detailed timing verification is required.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.