Loading

使用SystemC建模SystemVerilog状态机的实例

通过一个状态机的例子可以比较好的理解SystemC怎么建模RTL。

我们以一个典型的SystemVerilog编写的状态机为例。

fsm.sv:

module fsm( 
    input clk,
    input rst_n,
    input [1:0] in,
    output logic [1:0] out
);

enum logic [1:0] {
    IDLE = 2'b00,
    RUN  = 2'b01,
    STOP = 2'b10
} current_state, next_state;

always_ff @( posedge clk, negedge rst_n ) begin : state_update
    if (!rst_n)
        current_state <= IDLE;
    else
        current_state <= next_state;
end

always_comb begin : state_trans
    next_state = current_state;
    case (current_state)
        IDLE: if(in==2'b01) next_state = RUN;
        RUN: if(in==2'b10) next_state = STOP;
        STOP: if(in==2'b11) next_state = IDLE;
    endcase 
end

always_comb begin : set_output
    out = 2'b00;
    case (current_state)
        IDLE: out = 2'b01;
        RUN: out = 2'b10;
        STOP: out = 2'b11; 
    endcase
end

endmodule

tb_fsm.sv:

`timescale 1ns/1ns
module tb_fsm;

logic clk;
logic rst_n;
logic [1:0] in;
logic [1:0] out;

initial begin
    clk = 1'b1;
    rst_n = 1'b0;
    in = 2'b00;
    #0;
    rst_n = 1'b1;
    #2;
    in = 2'b01;
    #2;
    in = 2'b10;
    #2;
    in = 2'b11;
    #2;
    $finish;
end

always #1 clk = ~clk;

fsm u_fsm_0 (
    .clk(clk),
    .rst_n(rst_n),
    .in(in),
    .out(out)
);

initial begin
    $fsdbDumpfile("tb_fsm.fsdb");
    $fsdbDumpvars;
end

endmodule

运行结果:

image

我们编写等效的SystemC模型:

fsm.h:

#ifndef _FSM_H_
#define _FSM_H_

#include <systemc.h>
#include <iostream>

SC_MODULE(fsm) {
    sc_in<bool> clk;
    sc_in<bool> rst_n;
    sc_in<sc_uint<2>> in;
    sc_out<sc_uint<2>> out;

    void state_update();
    void state_trans();
    void set_output();
    void trace();
    void traceVCD(sc_trace_file* file);

    SC_CTOR(fsm)
    {
        SC_METHOD(state_update);
        sensitive << clk.pos() << rst_n.neg();
        SC_METHOD(state_trans);
        sensitive << in << current_state;
        SC_METHOD(set_output);
        sensitive << current_state;
        SC_METHOD(trace);
        sensitive << clk.pos();
    }

    enum state {IDLE=0,RUN=1,STOP=2};

    sc_signal<sc_uint<2>> current_state;
    sc_signal<sc_uint<2>> next_state;
};

#endif

fsm.cpp:

#include "fsm.h"

using namespace std;

void fsm::state_update()
{
    if(rst_n == false)
        current_state = IDLE;
    else
        current_state = next_state;
}

void fsm::state_trans()
{
    switch (current_state.read())
    {
    case IDLE:
        if(in.read() == 1) next_state = RUN;
        break;
    case RUN:
        if(in.read() == 2) next_state = STOP;
        break;
    case STOP:
        if(in.read() == 3) next_state = IDLE;
        break;
    default:
        next_state = current_state;
        break;
    }
}

void fsm::set_output()
{
    switch (current_state.read())
    {
    case IDLE:
        out = 1;
        break;
    case RUN:
        out = 2;
        break;
    case STOP:
        out = 3;
        break;
    default:
        out = 0;
        break;
    }
}

void fsm::trace()
{
    cout << sc_time_stamp() << " in: " << in << " current_state: " << current_state << " next_state: " << next_state << " out: " << out << endl;
}

void fsm::traceVCD(sc_trace_file* file)
{
    sc_trace(file,clk,"clk");
    sc_trace(file,rst_n,"rst_n");
    sc_trace(file,in,"in");
    sc_trace(file,out,"out");
    sc_trace(file,current_state,"current_state");
    sc_trace(file,next_state,"next_state");
}

tb_fsm.cpp

#include "fsm.h"

SC_MODULE(tb_fsm) {
  sc_signal<bool> rst_n;
  sc_signal<sc_uint<2>> in;
  sc_signal<sc_uint<2>> out;
  void test()
  {
    rst_n = 0;
    in = 0;
    wait(SC_ZERO_TIME);
    rst_n = 1;
    wait(3,SC_NS);
    in = 1;
    wait(2,SC_NS);
    in = 2;
    wait(2,SC_NS);
    in = 3;
  }
  SC_CTOR(tb_fsm)
  {
    SC_THREAD(test);
  }
};

int sc_main(int, char*[]) {
  sc_clock clk("clk", 2, SC_NS);

  tb_fsm u_tb_fsm("u_tb_fsm");
  fsm u_fsm("u_fsm"); 

  u_fsm.clk(clk);
  u_fsm.rst_n(u_tb_fsm.rst_n);
  u_fsm.in(u_tb_fsm.in);
  u_fsm.out(u_tb_fsm.out);

  sc_trace_file* file = sc_create_vcd_trace_file("tb_fsm");
  u_fsm.traceVCD(file);
  sc_start(12, SC_NS);
  sc_close_vcd_trace_file(file);
  return 0;
}

运行结果:

0 s in: 0 current_state: 0 next_state: 0 out: 0
0 s in: 0 current_state: 0 next_state: 0 out: 1

Info: (I702) default timescale unit used for tracing: 1 ps (tb_fsm.vcd)
2 ns in: 0 current_state: 0 next_state: 0 out: 1
4 ns in: 1 current_state: 0 next_state: 1 out: 1
6 ns in: 2 current_state: 1 next_state: 2 out: 2
8 ns in: 3 current_state: 2 next_state: 0 out: 3
10 ns in: 3 current_state: 0 next_state: 0 out: 1

用vcd2fsdb将生成的vcd波形文件转换成fsdb格式:
image

fsdb导入到verdi:

image

可以看到和sv的结果一致,注意sc的默认最小仿真时间单位为1ps。

我们总结规律就可以知道,SystemVerilog里面的端口部分就对应SystemC里面用sc_port描述的部分,SystemVerilog里面的信号就对应SystemC里面用sc_signal描述的部分,SystemVerilog里面的always块对应SystemC里面注册的SC_METHOD,SystemVerilog里面例化对应SystemC创建一个module的对象,然后port连接对应的signal即可。

需要注意的是,这种1:1建立精确时钟的模型虽然在SystemC能力范围内,但不是常见的做法,SystemC的主要优势在于事务级建模。

换而言之,我大可以写一个不是时间驱动的模型,就写几个功能之间相互触发,这样的话因为不涉及时序,可以快速的去验证功能,类似于完全在做算法级的验证,这样写出来的东西可以把各个模块的端口和行为给大概弄清楚,也可以用来做软件的开发。当我需要去测试性能或者说需要去cycle by cycle的建模时,我再考虑把clock给引入进来。

在进行系统开发时,其实很多时候我在从顶向下设计时我只是优先设计了模块之间的端口和他们之间会做什么样的交互,但是每个模块具体的时序我实际上并不care,因为那是rtl开发时才会关心的事情了。所以我在快速开发时,大可以省去时钟驱动的环节,把我关心的模块端口实现出来,并且重点关注他们内部的行为,即对其他模块会发什么东西,接收其他模块的某个触发信号后会有怎样的行为等。

所以后续的工作流应该是,写spec,写systemc模型,先写时序松散型或时间近似型或无时间型,功能验证完毕,丢给rtl那边做实现,丢给软件那边做应用开发。也符合top-down设计流程的一个基本思想,这样设计模块时也就不是简单的凭空想了,而是可以快速建模快速验证了,写好之后的代码直接就是一个spec了。

可以参考这篇博客

posted @ 2023-12-13 21:44  sasasatori  阅读(361)  评论(0编辑  收藏  举报