SystemC 学习之与 Verilog 的混合仿真(十)

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

运行结果如下所示

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值