简介
SystemC的意义,网上能查到,这里总结一下,System C是C++的library,类似UVM是systemverilog的library
下图是SystemC在整个项目中的角色
- 硬件架构探索,创建算法、性能模型;
- 验证工程师做为参考模型(经过DPI接口调用);
- 设计工程师将其做为design spec,设计RTL;
- 软件工程师做为软件开发的硬件模型;
- 使多种提早测试成为可能
一 按照
去官网下载tar包, 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!"<<endl;
}
};
#endif
#include "hello.h"
int sc_main(int argc, char * argv[]) {
hello h("hello");
return 0;
}
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
某些公司是module load systemc, 然后echo $SYSTEMC_HOME,试试
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<sc_uint< 8 >> din; // sc_in< sc_logic > a[32];
sc_out<sc_uint<8>>dout;
sc_inout<sc_uint<8>> dinout;
sc_in_clk clk;
sc_signal<bool> S1, S2, S3;
方法
read() // mem[addr.read()]wr_data.read();
write()
时钟
typedef sc_in<bool> sc_in_clk; 所以sc_in_clk 跟sc_in<bool>是等价的
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<<clk.pos();
}
SC_THREAD
sc_thread 能够被挂起和重新激活。 sc_thread使用wait()挂起, 当敏感列表中有事件发生,进程被重新激活运行到新的wait语句,在一次仿真中thread一旦退出,将不再进入。
eg: SC_THREAD(main);
sensitive<<clk.pos();
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<<clk.pos()<<rst_n.pos();
如果进程不希望在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.
接口是一个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 T>
class mem_read_if: public sc_interface {
public:
virtual transfer_status read(unsigned int addr, T& data) = 0;
}
template <class T>
class mem_write_if: public sc_interface {
public:
virtual transfer_status write(unsigned int addr, T& data) = 0;
}
template <class T>
class reset_if: public sc_interface {
public:
virtual bool reset() = 0;
}
template <class T>
class mem_if: public mem_write_if<T>, mem_read_if<T>, reset_if<T> {
public:
virtual unsigned int start_addr() const = 0;
virtual unsigned int end_addr() const = 0;
}
port(端口)
- sc_in<T>;
- sc_out<T>;
- sc_inout<T>;
- 自定义端口: sc_port<Interface Type, ChannelNumber=1>
自定义端口:
一个端口可以同时连接到一个或者多个实现了同一接口的通道上:
格式: sc_port<Interface Type, ChannelNumber=1>
sc_port<ram_if, N> ram_portN; 基类是sc_port<IF, N>;
sc_port<IF,N> 是所有端口基类
Channel (通道)
系统抽象的3个关键:
- 行为 -> module
- 时序
- 通信 -> 通道
通道才是接口方法的实现者
通道本身也可能是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"<<tlm::tlm_release()<<endl;
cout<<"tlm version:\n\t"<<tlm::tlm_version()<<endl;
cout<<"tlm copyright:\n\t"<<tlm::tlm_copyright()<<endl;
TLM2基本组件
- tlm_initiator_socket 和tlm_target_socket
- tlm_generic_payload
- tlm_phase (非阻塞传送接口模板类的缺省相位类型)
- 阻塞和非阻塞传送接口, 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_model*>(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
可以先参考下面的文档
UVM TLM2: SystemC和SV 通信_sv 与 c 通信-CSDN博客
下面网址可以下载uvmc library
tips:
sc, sv 沟通的transaction可以通过uvm_object_utils来关联,但是顺序必须一样
SV to SC
一种是sv中的init直接连接sc中的target,另一种可以在tb中写一个adapter,使用passthrough做切割
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 = {<<byte{vif.monclk.wdata}};
gp.set_address(vif.monclk.addr);
gp.set_data(wdata);
init_skt.b_transport(gp, delay);
endtask
endclass
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<top_module> 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<cls_name, 32, struct> 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 <iostream>
#include "dpi_export.h"
#include "systemc.h"
#include "tlm.h"
#include "tlm_utils/simple_initiator_socket.h" //SYSTEMC_HOME
#include "tlm_utils/simple_target_socket.h" //SYSTEMC_HOME
#include "../uvmc-2.3.2/src/connect/sc/uvmc.h" //UVMC_HOME
//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<sc_model> out;
tlm_utils::simple_target_socket<sc_model> 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"<<endl;
wait(delay); // for uvm connect phase
for (int i = 32; i < 36; i += 4) {
tlm::tlm_command cmd = static_cast<tlm::tlm_command>(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<unsigned char*>(&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"<<endl;
}
}
virtual void in_b_transport(tlm_generic_payload& trans, sc_time& delay) {
tlm::tlm_command cmd = trans.get_command();
sc_dt::uint64 adr = trans.get_address();
unsigned char* ptr = trans.get_data_ptr();
//unsigned int len = trans.get_data_length();
//unsigned char* byt = trans.get_byte_enable_ptr();
//unsigned int wid = trans.get_streaming_width();
cout<<"SC: b_transport addr="<<adr<<endl;
//trans.set_response_status(tlm::TLM_OK_RESPONSE);
//if (trans->is_response_error() )
// SC_REPORT_ERROR("TLM-2", "Response error from b_transport");
}
// sc_main
int sc_main(int argc, char* argv[]) {
cout<<"sc_main"<<endl;
sc_model sc("sc_model");
uvmc_connect(sc.out, "out");
uvmc_connect(sc.in, "in");
sc_start();
return 0;
};
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 = {<<byte{5}};
gp.set_address(16);
gp.set_data(wdata);
ini_skt.b_transport(gp, delay);
$display("SV: transfer");
end
#2000;
phase.drop_objection(this);
endtask
endclass: my_uvm_test
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