SystemC Study

 

简介

  SystemC的意义,网上能查到,这里总结一下,System C是C++的library,类似UVM是systemverilog的library

下图是SystemC在整个项目中的角色

  • 硬件架构探索,创建算法、性能模型;
  • 验证工程师做为参考模型(经过DPI接口调用);
  • 设计工程师将其做为design spec,设计RTL;
  • 软件工程师做为软件开发的硬件模型;
  • 使多种提早测试成为可能

SystemC Study_第1张图片

一 按照

去官网下载tar包, https://www.accellera.org/downloads/standards/systemc

我下载的是SystemC 2.3.3 (Includes TLM), Core SystemC Language and Examples (tar.gz)

在linux上安装

1.放到linux 任意路径,tar -zxvf 解包

2.在systemc-2.3.3中有一个INSTALL的文件,安装步骤就可以了

    2.1 mkdir objdir; 2.2 cd objdir;  2.3 export CXX=g++ or setenv CXX g++ 2.4 ../configure --prefix=/usr/local/systemc (重新指定安装路径) 2.5 make; 2.6 make check 2.7 make install 2.9 cleean

example 测试

在安装的文件下有 example, 进入make,

如果报错的话根据提示看一下就好,遇到了TARGET_ARCH, 使用的机器是linux64, default 这个变量是空的

二 Start with example

这里写了一个简单的hello world, 并自己写了一个makefile

example: hello world

  例子没什么好说的,System C电子系统设计一开始就把code show出来了

#ifndef _HELLO_H
#define _HELLO_H
#include "systemc.h"

SC_MODULE(hello) {
  SC_CTOR(hello) {
    cout<<"Hello SystemC!"<

makefile

g++ -O2 -Wall -Werror -Wno-unused-variable -Isrc -I/user/systemc/include -c main.cpp -o main.o
g++ -O2 -Wall -Werror -Wno-unused-variable -L/user/systemc/lib-linux64 -Wl,-rpath=/user/systemc/lib-linux64 -lsystemc -lm -o com main.o

这里可能会报找不到.so, 需要加入-Wl,-rpath=xxx, 把路径指过去,或者设置环境变量LD_LIBRARY_PATH

CXX := g++
GCCVERSION := $(shell expr `gcc -dumpversion`)

#FLAGS_STRICT = -pedantic -Wno-long-long

DEBUG   = -g
CXXFLAGS= -O2 -Wall -Werror -Wno-unused-variable
SYSTEMC = /user/systemc
INCDIR  = -Isrc -I$(SYSTEMC)/include
LIBDIR  = -L$(SYSTEMC)/lib-linux64
LIBS    = -lsystemc -lm
RPATH   = $(SYSTEMC)/lib-linux64

SRCS := $(wildcard ./*.cpp)

OBJS = $(patsubst %cpp, %o, $(SRCS)) main.o

%.o: %.cpp
	echo "Compile " $< " to " $@
	$(CXX) $(CXXFLAGS) $(INCDIR) -c $< -o $@

all: $(OBJS)
	$(CXX) $(CXXFLAGS) $(LIBDIR) -Wl,-rpath=$(RPATH) $(LIBS) -o $@ $^ 
    ./all

clean:
    rm -rf *.o *.out ./com

 example: nand2 with testbench

这个例子来自于System C电子系统设计,这里记录遇到的问题

1.sc_start(100, SC_NS); // 2.3.3版本需要把clock 单位加上

2.vcd 文档打开,simvision/verdi, open database 打开就行

 

三 基本概念

SC_MODULE

SC_MODULE(module)

实际上SC_MODULE的code如下: #define SC_MODULE(module_name) struct module_name : public sc_module, 所以也可以使用C++的方式定义module,class module : public sc_module; 两者区别是前者默认所有的成员是public的,而后者是private的

构造函数和析构函数

1. SC_CTOR

    SC_CTOR(module_name) 中定义了SC_METHOD等3个进程所需要的一些定义

2.传统C++的方式

   SC_AS_PROCESS(module_name);

  module_name ( sc_module_name n): sc_module(n) {...}

析构函数: ~module_name(){...}

端口

sc_in> din; // sc_in< sc_logic > a[32];

sc_out>dout;

sc_inout> dinout;

sc_in_clk clk;

sc_signal S1, S2, S3; 

方法

read() // mem[addr.read()]wr_data.read();

write()

时钟

typedef sc_in sc_in_clk; 所以sc_in_clk 跟sc_in是等价的

sc_clock 类: 参数(name, period, 时间单位, 占空比, 开始时间, 第一个逻辑是低电平还是高电平)

eg: sc_clock clk("fclk", 20, SC_NS, 0.5, 5, true); 

推荐写法: 1. sc_clock clk1("clk1", 20, SC_NS); 2. sc_clock clk2("clk2", 20, SC_NS, 0.5);

 

基本数据类型

操作符:

, : (a, b) 将a, b 串起来, 类似位拼接

range(left, range): 位选择 a[left, range]

位减操作:and_reduce, 类似&A

任意精度整型数据类型

sc_bigint

sc_biguint

可以通过自定义SC_MAX_NBITS提高速度, 推荐SC_MAX_NBITS定义为BITS_PER_DIGIT(30)的整数倍

用户自定义结构体

typedef struct _frame {...} frame;

typedef struct _frame {
  unsigned short frame_constrol;
  char src_addr[6];
  char dst_addr[6];
  char *body;
} frame;

进程

System C 基本进程有3种

1. SC_METHOD

2. SC_THREAD

3. SC_CTHREAD

SC_METHOD

sc_method 需要敏感列表,不能使用wait()这样的语句,避免死循环,防止时间停止。 当敏感列表中有事件发生,它会被调用,调用后立刻返回。

每个clk的上升沿, main被执行一次。

SC_CTOR(tb) {
  SC_METHOD(main);
  sensitive<

SC_THREAD

sc_thread 能够被挂起和重新激活。 sc_thread使用wait()挂起, 当敏感列表中有事件发生,进程被重新激活运行到新的wait语句,在一次仿真中thread一旦退出,将不再进入。

eg: SC_THREAD(main);

      sensitive<

SC_CTHREAD

Clock Thread Process, 是一种特殊的thread,继承于thread, 但是只能在时钟上升沿或者下降沿被触发。

SC_CTOR(tb){
  SC_CTHREAD(gen_input,clk.pos());
  reset_signal_is(rst_i, true);
}

wait() and next_trigger()

wait() 只能用于thread/Cthread. 参数有下面种

1.wait(); 等待敏感列表中的事件

2. wait(const sv_event&) ; 等待事件发生,eg:  sc_event e1; wait(e1);

3. wait(sc_event_or_list &); 等待事件之一发生; eg: sc_event e1, e2, e3; wait(e1|e2|e3); 

4. wait(sc_event_and_list &); 等待事件全部发生; 

5. wait(200, SC_NS)

6. wait(200, SC_NS, e1); 在200NS内等待e1, 如果期间等不到后,就不再等带e1

next_trigger(); 只用于SC_METHOD

dont_initialize() and sensitive

SC_METHOD and SC_THREAD必须有敏感列表, 使用sensitive设置敏感列表: sensitive<<敏感列表<<敏感列表N; sensitive<

如果进程不希望在0时刻执行,此时可以使用dont_initialize();

SystemC 波形dump

  // trace file creation
  sc_trace_file * tf = sc_create_vcd_trace_file("Nand2");
  sc_trace(tf, N2.A, "A");
  sc_trace(tf, N2.B, "B");
  sc_trace(tf, N2.F, "F");
  sc_start(200, SC_NS);
  sc_close_vcd_trace_file(tf);

systemC 允许仿真结果dump位VCD(Value Change Dump)格式,

dump struct data需要重载sc_trace();

struct packet {
  char src_addr;
  char dst_addr;
  int  payload;
}

void sc_trace(sc_trace_file *tf, const packet v, const string name) {
  int i;
  sc_trace(tf, v.src_addr, name+".src_addr");
  sc_trace(tf, v.dst_addr, name+".dst_addr");
  sc_trace(tf, v.payload,  name+".payload");
}

 

SystemC 报告机制

1.常用macro:

  SC_REPORT_INFO(msg_type, msg)

  SC_REPORT_WARNING(msg_type, msg)

  SC_REPORT_ERROR

  SC_REPORT_FATAL

  sc_assert(expr)

主要使用sc_report_handler and sc_report, SC_REPORT_INFO <-> sc_report_handler::report(SC_INFO, msg_type, msg, _FILE_, _LINE_);

四 SystemC 行为建模

TLM 知识

systemc 中定义了interface, port, channel.

SystemC Study_第2张图片

接口是一个C++抽象类,通道实现一个或者多个接口

接口中定义的抽象方法在通道中实现

interface

1.接口是C++抽象类

2. 接口的所有方法用“=0”标识是纯虚函数

3.C++ 不允许创建抽象类对象

4. sc_interface是所有接口的基类

5.接口不包括任何数据成员

 

ram interface example

enum transfer_status { TRANSFER_OK=0, TRANSFER_ERROR};
template  
class mem_read_if: public sc_interface {
  public: 
    virtual transfer_status read(unsigned int addr, T& data) = 0;
}
template  
class mem_write_if: public sc_interface {
  public: 
    virtual transfer_status write(unsigned int addr, T& data) = 0;
}
template  
class reset_if: public sc_interface {
  public: 
    virtual bool reset() = 0;
}
template  
class mem_if: public mem_write_if, mem_read_if, reset_if {
  public: 
    virtual unsigned int start_addr() const = 0;
    virtual unsigned int end_addr() const = 0;
}

 

port(端口)

  1. sc_in;
  2. sc_out;
  3. sc_inout;
  4. 自定义端口: sc_port

自定义端口:

一个端口可以同时连接到一个或者多个实现了同一接口的通道上:

格式: sc_port

sc_port ram_portN; 基类是sc_port;

sc_port 是所有端口基类

 

Channel (通道)

系统抽象的3个关键:

  1. 行为 -> module
  2. 时序 
  3. 通信 ->  通道

通道才是接口方法的实现者

通道本身也可能是module

通道分为primitive channel 和hierarchical channel. 基本通道包括:sc_fifo, sc_signal, sc_signal_rv, sc_buffer, sc_semaphore, sc_mutex.

 

sc_mutex_if

互锁, class sc_mutex_if: virtual public sc_interface

sc_semaphore_if

sc_event

    notify()

     cancel()

eg: sc_event e; e.notify(2, SC_NS); wait(e);

 

TLM2

打印相关信息

  cout<<"tlm release:\n\t"<

TLM2基本组件

  1. tlm_initiator_socket 和tlm_target_socket
  2. tlm_generic_payload
  3. tlm_phase (非阻塞传送接口模板类的缺省相位类型)
  4. 阻塞和非阻塞传送接口, DMI和调试传送接口

 

 

SystemC with UVM via DPI

如果已经有DPI-C相关知识的话,这部分跟RTL-C 是一样的

verilog call C

C部分:

  extern C function, 目的是为了verilog能call 这部分function, 在verilog中调用call_systemc_function, 转到SC中sc_model__called_from_sv,在转到sc_model中的input_string_extern_c

SC_MODULE( sc_model ) {
  ...
  SC_CTOR( sc_model ) {
    ...
  }
};

// 
SC_MODULE_EXPORT(sc_model)

extern "C" {
  sc_model * sc_model__getScopeByName( const char* inst ) {
    sc_model* scope = NULL;
    scope = dynamic_cast(sc_find_object(inst));
    vpi_printf("DPI PRINT:: sc_model__getScopeByName returning scope of sc_model\n");
    return scope;
  }

  void sc_model__linkSvScope( sc_model * scope ) {
    vpi_printf("DPI PRINT:: sc_model__linkSvScope called, giving scope of SV interface to SC model \n");
    scope->setScope( svGetScope() );
  }

  int sc_model__called_from_sv (sc_model *scope, const char* input_string) {
    vpi_printf("DPI PRINT:: sc_model__called_from_sv function called from %s\n", input_string);
    // the scope has been set to the right model so calling the function in that model
    return scope->input_string_extern_c(input_string);
  }

}

verilog

interface 中关联与SC的function

interface sc_model_if ();
  import uvm_pkg::*;

  event my_event; //*< event triggered by call by SystemC model
  chandle scope; //*< C instance scope

  // called by interface code to get chandle for C instance scope
  import "DPI-C" pure function chandle sc_model__getScopeByName   ( input string  inst );
  // called by interface code to establish callback scope for model
  import "DPI-C" context function void sc_model__linkSvScope( chandle scope );

 // importing the functions and tasks that we need to be able to access in the SystemC model
  import "DPI-C" context task sc_model__called_from_sv   ( chandle scope, input string input_string );
  // exporting functions that we want the c model to be able to call
  export "DPI-C" function sv_called_from_sc_model;
 
  string sc_model_hierarchy;

  task call_systemc_function(input string input_string);
    if( scope != null )
       sc_model__called_from_sv  ( scope, input_string );
    else
       `uvm_fatal( $sformatf("%m"), "Call to call_systemc_function before connecting to hierarchy" )
  endtask

  function void sv_called_from_sc_model (input string input_string);
    `uvm_info($sformatf("%m"), $sformatf("function sv_called_from_sc_model called by the SystemC model with: %s", input_string), UVM_MEDIUM )
    `uvm_info($sformatf("%m"), "triggering event to notify the UVM environment", UVM_MEDIUM )
    -> my_event;
  endfunction

  task wait_for_event();
     @(my_event);
     // or perhaps wait(my_event.triggered()); to avoid races if its not triggered multiple times in a time step
  endtask

endinterface

C call verilog

C 部分

在构造函数中加入SC_THREAD

SC_MODULE(sc_model){
  SC_CTOR( sc_model ) {
    SC_THREAD(call_verilog);
  }
  void call_verilog() {
    ostringstream msg;
    msg << "Entering function call_verilog";
    SC_REPORT_INFO( "SYSTEMC:: ", msg.str().c_str() );
    wait(5, SC_NS);
    svSetScope(sv_if);
    sv_called_from_sc_model("string passed from SystemC model");
  }

}

verilog 部分

interface sc_model_if ();
  import uvm_pkg::*;
  // exporting functions that we want the c model to be able to call
  export "DPI-C" function sv_called_from_sc_model;


  function void sv_called_from_sc_model (input string input_string);
    `uvm_info($sformatf("%m"), $sformatf("function sv_called_from_sc_model called by the SystemC model with: %s", input_string), UVM_MEDIUM )
    `uvm_info($sformatf("%m"), "triggering event to notify the UVM environment", UVM_MEDIUM )
    -> my_event;
  endfunction

endinterface

Xcelium command:

elaborate

xrun +UVM_NO_RELNOTES -xmlibdirname tmp_xcelium.d -l gen_header.log -64bit -licqueue -sv \
-uvm -vtimescale 1ns/1ns -f filelist -elaborate \
-dpi \
-dpiheader dpi_export.h \
-dpiimpheader dpi_import.h 
 

run

xrun -quiet -64bit -licqueue -sv +UVM_NO_RELNOTES -uvm +UVM_VERBOSITY=HIGH \
# SystemC model
-sysc -I.
../src/sc_model.cpp
-scautoshell verilog

# Verilog testbench
-vtimescale 1ns/1ns -f filelist 

# DPI export
-dpi

#debug
-g
#-tcl
#-input debug.tcl
-linedebug
#-uvmlinedebug

 

SystemC with UVM via TLM2

可以先参考下面的文档

https://blog.csdn.net/ocarvb/article/details/108450311

 

下面网址可以下载uvmc library

https://verificationacademy.com/cookbook/uvm-connect

tips:

  sc, sv 沟通的transaction可以通过uvm_object_utils来关联,但是顺序必须一样

 

SV to SC

一种是sv中的init直接连接sc中的target,另一种可以在tb中写一个adapter,使用passthrough做切割

SystemC Study_第3张图片

1. uvm agent monitor中发出command

class my_monitor extends uvm_component;
  `uvm_component_utils(my_monitor)
  uvm_tlm_b_initiator_socket#(uvm_tlm_generic_payload) init_skt;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    init_skt = new("init_skt", this);
  endfunction
  
  task run_phase(uvm_phase phase);
    super.run_phase(phase);
    uvm_tlm_generic_payload gp;
    uvm_tlm_time delay;
    byte unsigned wdata[];

    ......
    gp = uvm_tlm_generic_payload::type_id::create("gp");
    delay = new("delay");
    wdata = new[`CRB_DATA_WIDTH/8];
    wdata = {<

2. 在env将init socket 连接到SC层

adapter:

class b_socket_barrier #(type T=uvm_tlm_generic_payload) extends uvm_component;
    `uvm_component_param_utils(xxx_b_socket_barrier#(T))
    uvm_tlm_b_target_socket#(xxx_b_socket_barrier#(T),T) tgt_skt;
    uvm_tlm_b_initiator_socket#(T) init_skt;
    virtual task b_transport(T obj,uvm_tlm_time dly);
      init_skt.b_transport(obj,dly);
    endtask
endclass

env:
uvmc_pkg::uvmc_tlm#(uvm_tlm_generic_payload)::connect(agt.monitor.init_skt,"sv2sc_cmd");

3.在SC中实现socket, main中connect, 在module中通过register_b_transport 指定执行哪个function

#include "systemc.h"
using namespace sc_core;
using namespace sc_dt;
using namespace std;
#include "tlm.h"
using namespace tlm;

class top_module : public sc_module{
public:
  top_module (sc_module_name name); // constructure
  tlm_utils::simple_target_socket cmd_in;
}

SC_HAS_PROCESS(top_module);

top_module::top_module(sc_module_name name) : sc_module(name)
    ,cmd_in("cmd_in")
{
    cmd_in.register_b_transport(this,&top_module::cmd_b_transport);

}

void top_module::crb_b_transport(tlm_generic_payload &c, sc_time &t) { ... }

int sc_main(int argc, char* argv[]) {
  top_module top("top");
  uvmc_connect(top.cmd_in,"sv2sc_cmd");
}

SC 2 SV

1. SC 部分

module top;
multi_passthrough_initiator_socket out;
function:
   xxxx
   cmd.data = xxx;
   out->b_transport(cmd, t);
int sc_main(int argc, char* argv[]) {
  ...
  uvmc_connect(top.out, "out_0");
}

2. SV中接收

adapter:
class tlm2_to_tlm1_b_socket_barrier #(type T=uvm_tlm_generic_payload) extends uvm_component;
    `uvm_component_param_utils(tlm2_to_tlm1_b_socket_barrier#(T))
    uvm_tlm_b_target_socket#(tlm2_to_tlm1_b_socket_barrier#(T),T) tgt_skt;
    uvm_analysis_port #(T) m_ap;

    task b_transport(T obj,uvm_tlm_time dly);
    m_ap.write(obj);
  endtask
endclass

model:
uvm_analysis_port#(item) ap;
barrier.m_ap.connect(ap);
uvmc_pkg::uvmc_tlm#(item)::connect(barrier.tgt_skt,"out_0");

env:
model.ap.connect(m_scb.output_sb.expected_trans_fifo.analysis_export);

完整example

C code:

// because of dpi, so the code is not clean
#include 
#include "dpi_export.h"
#include "systemc.h"
#include "tlm.h"
#include "tlm_utils/simple_initiator_socket.h"
#include "tlm_utils/simple_target_socket.h"
#include "../uvmc-2.3.2/src/connect/sc/uvmc.h"

//using namespace tlm;
using namespace sc_core;
using namespace sc_dt;
using namespace std;
using namespace uvmc;
using namespace tlm;

SC_MODULE( sc_model ) {
  tlm_utils::simple_initiator_socket out;
  tlm_utils::simple_target_socket in;

  SC_CTOR( sc_model ) : out("out"), in("in") {
    in.register_b_transport(this, &sc_model::in_b_transport);//SV to SC
    SC_THREAD( call_tlm_c2v   ); // SC to SV
    msg << "System C model constructed" << name();
    SC_REPORT_INFO( "SYSTEMC:: CONSTRUCTOR:: ", msg.str().c_str() );
  }

  void call_tlm_c2v() {
    tlm::tlm_generic_payload* trans = new tlm::tlm_generic_payload;
    sc_time delay = sc_time(10, SC_NS);
    int data;
    cout<<"SC: start call c2v"<(rand() % 2);
      if (cmd == tlm::TLM_WRITE_COMMAND) data = 0xFF000000 | i;
      trans->set_command(cmd);
      trans->set_address(i);
      trans->set_data_ptr( reinterpret_cast(&data) );
      out->b_transport(*trans, delay);
      //if (trans->is_response_error())
      //  SC_REPORT_ERROR("TLM-2", "Response error from b_transport");
      cout<<"SC:transport done"<

SV code:

// the uvm agent is ready, here put all code on uvm_test
`include "uvm_macros.svh"
import uvm_pkg::*;
import uvmc_pkg::*;

class my_uvm_test extends uvm_test;
  `uvm_component_utils(my_uvm_test)

    uvm_tlm_b_initiator_socket#(uvm_tlm_generic_payload) ini_skt;
    uvm_tlm_b_target_socket#(my_uvm_test, uvm_tlm_generic_payload) tgt_skt; 

    function void build_phase (uvm_phase phase);
      ini_skt= new("ini_skt", this);
      tgt_skt = new("tgt_skt",this);
    endfunction // build_phase

    function void connect_phase(uvm_phase phase);
      uvmc_pkg::uvmc_tlm#(uvm_tlm_generic_payload)::connect(tgt_skt, "out");
      uvmc_pkg::uvmc_tlm#(uvm_tlm_generic_payload)::connect(ini_skt, "in");
      $display("connect done");
    endfunction

    // SC to SV, if have multi socket, can add a adapter class, and instance adapters
    // eg: uvmc_pkg::uvmc_tlm#(payload)::connect(barrier0.tgt_skt,"out_0");
    //     uvmc_pkg::uvmc_tlm#(payload)::connect(barrier1.tgt_skt,"out_1");
    //     b_transport is implement on the barrier class
    task b_transport(uvm_tlm_generic_payload obj,uvm_tlm_time dly);
      $display("SV: b_transport, addr=%0d", obj.get_address());
      //obj.set_response_status(tlm::TLM_OK_RESPONSE);
    endtask
    
    task run_phase(uvm_phase phase);
      phase.raise_objection(this);
      begin
        uvm_tlm_generic_payload gp;
        uvm_tlm_time delay;
        byte unsigned wdata[];

        gp = uvm_tlm_generic_payload::type_id::create("gp");
        delay = new("delay");
        wdata = new[1];
        wdata = {<

xcelium command:

xrun -quiet -64bit -licqueue-sv -mess -define DENALI_SV_NC -define DENALI_UVM
-uvmhome CDNS-1.1d
-loadvpi /tools/cadence/vip/11.30.066/tools.lnx86/denali_64bit/verilog/libcdnsv.so:cdnsvVIP:export
-top tb_top
-v2001
+UVM_NO_RELNOTES

-uvm +UVM_VERBOSITY=HIGH

-sysc -sc_main -I. ../src/sc_model.cpp -scautoshell verilog -DSC_INCLUDE_DYNAMIC_PROCESSES

-vtimescale 1ns/1ns ../src/my_pkg.sv -f uvmc-pkg.f ../src/tb_top.sv

-dpi  -g  

 

你可能感兴趣的:(SystemC,systemc)