1、SC 与 Verilog 的通信方式
Systemc 和 verilog 通信方式有两种,一种是 PLI,但是 PLI 只能 verilog 调用 c/c++,不能从 c/c++ 直接调用 verilog,想要从 c/c++ 调用 verilog 的话,需要先用 verilog 调用 c/c++ 函数,然后在 c++ 里面给对应的参数设置好值,然后 verilog 里面再拿这些数据,比较麻烦。还有一种是使用 DPI-C,DPI-C 是 system verilog 里面的,这样的话需要在编译的时候加上 -sverilog 编译选项
这里我们选用 DPI-C 将接口导入和导出,由于不会 verilog,所以对于 verilog 代码写的比较简单,如果有错误欢迎指正
2、一个简单例子
下面给出一个简单例子来说明 systemc 和 verilog 之间的数据传输
Makefile
SYSCAN = syscan -cpp g++ -cc gcc -tlm2 \
-cflags -g \
-cflags -DVCS \
-cflags -std=c++11 \
-cflags -I${VCS_HOME}/etc/systemc/tlm/include/tlm/tlm_utils \
-cflags -I${UVMC_HOME}/src/connect/sc \
-cflags -I${UVMC_HOME}/src \
-cflags -Icpp \
${UVMC_HOME}/src/connect/sc/uvmc.cpp \
${UVMC_HOME}/src/connect/sc/uvmc_export_stubs.cpp
VLOGAN = vlogan -q -sverilog \
+incdir+${UVM_HOME}/src ${UVM_HOME}/src/uvm_pkg.sv \
+incdir+${UVMC_HOME}/src/connect/sv ${UVMC_HOME}/src/connect/sv/uvmc_pkg.sv \
-timescale=1ns/1ps
VCS_ELAB = vcs -q -sysc=deltasync -lca \
-sysc -cpp g++ -cc gcc \
-timescale=1ns/1ps \
-CFLAGS -DVCS ${UVM_HOME}/src/dpi/uvm_dpi.cc
CURRENT_DIR = $(shell pwd)
CPP_DIR = $(shell find $(CURRENT_DIR)/cpp -maxdepth 20 -type d)
SRCS_CPP += $(foreach dir, $(CPP_DIR), $(wildcard $(dir)/*.cpp))
SRCS_CC += $(foreach dir, $(CPP_DIR), $(wildcard $(dir)/*.cc))
SRCS_C += $(foreach dir, $(CPP_DIR), $(wildcard $(dir)/*.c))
VERILOG_DIR = $(shell find $(CURRENT_DIR)/verilog -maxdepth 20 -type d)
SRCS_VERILOG += $(foreach dir, $(VERILOG_DIR), $(wildcard $(dir)/*.v))
comp:
$(VLOGAN) -full64 $(SRCS_VERILOG) +define+UVM_OBJECT_MUST_HAVE_CONSTRUCTOR
$(SYSCAN) -full64 $(SRCS_CPP) $(SRCS_CC) $(SRCS_C)
$(VCS_ELAB) -full64 verilog_main sc_main # 这里要写 verilog、sv、sc 对外的模块名字
clean:
rm -rf simv* work csrc ucli.key vc_hdrs.h vcs.log AN* *.log *.log.cmp *.vpd DVE* .vlogan*
run:
./simv
Systemc 模块
对外的头文件,extern 表示要调用的 verilog 接口
// sc2v.h
#ifndef SC2V_H
#define SC2V_H
extern "C" {
// export
void VerilogSendToSCModule(char* data, int len);
void SaveScope();
// import
extern void SCSendToVerilog(char* data, int len);
}
#endif // SC2V_H
// sc2v.cpp
#include "sc2v.h"
#include <iostream>
#include "instance_manager.h"
void VerilogSendToSCModule(char* data, int len) {
std::shared_ptr<DataManager> data_manager = InstanceManager::CreateInstance()->GetDataManager("receive_module");
data_manager->ReceiveData(data, len);
}
void SaveScope() {
std::cout << "SaveScope" << std::endl;
InstanceManager::CreateInstance()->my_scope = svGetScope();
InstanceManager::CreateInstance()->init_ = true;
}
因为 SCSendToVerilog 代码是在 verilog 里面的,单纯编译 c++ 代码会报错,所以这里要在 c++ 里面声明一个弱符号
// sc2v_stubs.cpp
#include <cstdio>
#include "sc2v.h"
void SCSendToVerilog(char* data, int len) __attribute__((weak));
// 不然 c++ 会报错
void SCSendToVerilog(char* data, int len) {
printf("fake func\n");
}
c++ 收到 verilog 发过来的数据后,先将数据存放在一个队列里面,然后 systemc 有一个进程以一定时钟周期访问这个队列获取数据
// data_manager.h
#pragma once
#include <string>
#include <queue>
class DataManager {
public:
DataManager();
~DataManager();
void ReceiveData(const std::string& data, int len);
bool GetData(std::string& data);
private:
std::queue<std::string> data_queue_{};
};
// data_manager.cpp
DataManager::DataManager() = default;
DataManager::~DataManager() = default;
void DataManager::ReceiveData(const std::string& data, int len) {
data_queue_.push(data);
}
bool DataManager::GetData(std::string& data) {
if (data_queue_.empty()) {
return false;
}
data = data_queue_.front();
data_queue_.pop();
}
// instance_manager.h
#pragma once
#include <unordered_map>
#include <memory>
#include <string>
#include <svdpi.h>
#include "data_manager.h"
class InstanceManager {
public:
static InstanceManager* CreateInstance();
void Init();
std::shared_ptr<DataManager> GetDataManager(const std::string& module_name);
public:
svScope my_scope;
bool init_ = false;
private:
InstanceManager();
~InstanceManager();
InstanceManager(const InstanceManager&) = delete;
InstanceManager operator=(const InstanceManager& ) = delete;
private:
std::unordered_map<std::string, std::shared_ptr<DataManager>> data_manager_map_{};
};
// instance_manager.cpp
#include "instance_manager.h"
const std::string receive_name = "receive_module";
InstanceManager* InstanceManager::CreateInstance() {
static InstanceManager* instance = new InstanceManager();
return instance;
}
std::shared_ptr<DataManager> InstanceManager::GetDataManager(const std::string& module_name) {
if(data_manager_map_.find(module_name) == data_manager_map_.end()) {
return nullptr;
}
return data_manager_map_[module_name];
}
void InstanceManager::Init() {
data_manager_map_[receive_name].reset(new DataManager());
}
InstanceManager::InstanceManager() = default;
InstanceManager::~InstanceManager() {
data_manager_map_.clear();
}
// receiver.h
#pragma once
#include <systemc.h>
class Receiver : public sc_module {
public:
SC_HAS_PROCESS(Receiver);
Receiver(sc_module_name ins_name);
~Receiver();
void ReceiverData();
public:
sc_in_clk clk;
};
// receiver.cpp
#include "receiver.h"
#include "instance_manager.h"
Receiver::Receiver(sc_module_name ins_name) : sc_module(ins_name) {
SC_METHOD(ReceiverData);
sensitive << clk.pos();
dont_initialize();
}
Receiver::~Receiver() = default;
void Receiver::ReceiverData() {
std::shared_ptr<DataManager> data_manager = InstanceManager::CreateInstance()->GetDataManager("receive_module");
std::string data;
if (data_manager->GetData(data)) {
std::cout << sc_time_stamp() << " " << data << std::endl;
}
}
// sender.h
#pragma once
#include <systemc.h>
#include <string>
class Sender : public sc_module {
public:
SC_HAS_PROCESS(Sender);
Sender(sc_module_name instname);
~Sender();
void SendData();
public:
sc_in_clk clk;
private:
int val_{};
};
// sender.cpp
#include "sender.h"
#include <string>
#include "instance_manager.h"
#include "sc2v.h"
Sender::Sender(sc_module_name instname) : sc_module(instname) {
SC_METHOD(SendData);
sensitive << clk.pos();
dont_initialize();
}
Sender::~Sender() = default;
void Sender::SendData() {
if(!InstanceManager::CreateInstance()->init_) {
return;
}
std::string data = "systemc " + std::to_string(val_++);
svSetScope(InstanceManager::CreateInstance()->my_scope);
SCSendToVerilog((char*)data.c_str(), data.length());
}
// main.cpp
#include <systemc.h>
#include "receiver.h"
#include "sender.h"
#include "instance_manager.h"
int sc_main(int argc, char* argv[]) {
InstanceManager::CreateInstance()->Init();
Receiver receiver("receiver");
Sender sender("sender");
sc_clock clk("clk", 20, SC_NS);
receiver.clk(clk);
sender.clk(clk);
sc_start(200, SC_NS);
return 0;
}
Verilog 模块
import:表示 verilog 调用 c++ 的接口
export:导出接口,表示提供给 c++ 可以调用的接口
module verilog_main;
import "DPI-C" context function VerilogSendToSCModule(string data, int len);
import "DPI-C" context function void SaveScope();
export "DPI-C" function SCSendToVerilog;
function void SCSendToVerilog(string data, int len);
$display("Verilog::data:%s", data);
VerilogSendToSCModule(data, len);
endfunction
initial begin
SaveScope();
end
endmodule
这里调用 SaveScope 是因为只有在 verilog 初始化之后才能拿到当前的 scope,每次 c++ 传输数据给 verilog 时需要先设置 scope,然后才能发送数据
编译运行
make comp
./simv
运行结果如下所示