SystemVerilog和SystemC协同验证环境介绍(2)

hegangben
2025-06-06 / 0 评论 / 5 阅读 / 正在检测是否收录...

mbkkrpvp.png

1、下载 uvmc (uvm connect)
https://download.csdn.net/download/yp18792574062/88529417?spm=1001.2014.3001.5501

2、配置相关环境变量

export UVM_HOME=${VCS_HOME}/etc/uvm
export UVMC_HOME=/home/yangpan/yangpan/uvmc/uvmc-2.3.1

然后执行 source ~/.zshrc 更新

3、编译 uvmc 自带的例子
编译之前需要更新 gcc 的版本,这里将 gcc 版本降低到 5.2.0,因为 vcs2018 只支持少数几个 gcc 版本,降低 gcc 的版本可以参考之前的文章

cd examples/converters
ln -sf Makefile.vcs Makefile
make comp EXAMPLE=sv2sc
./simv
make comp EXAMPLE=sc2sv
./simv

4、自己动手写一个 sc 到 sv 再到 sc 的例子
因为对 system verilog 不熟悉,所以这里 sv 在接收到数据后就立马发送给 sc,实现一个数据的回传效果

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
 
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))
SV_DIR = $(shell find $(CURRENT_DIR)/sv -maxdepth 20 -type d)
SRCS_SV += $(foreach dir, $(SV_DIR), $(wildcard $(dir)/*.sv))
 
comp:
    $(SYSCAN) -full64 $(SRCS_CPP) $(SRCS_CC) $(SRCS_C)
    $(VLOGAN) -full64 $(SRCS_SV) +define+UVM_OBJECT_MUST_HAVE_CONSTRUCTOR
    $(VCS_ELAB) -full64 sv_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 发送模块

// sender.h
#pragma once
#include "systemc.h"
#include <string>
#include <iomanip>
#include "uvmc.h"
using namespace uvmc;
#include <tlm.h>
using namespace sc_core;
using namespace tlm;
#include "simple_initiator_socket.h"
using tlm_utils::simple_initiator_socket;
 
class producer : public sc_module {
public:
    SC_HAS_PROCESS(producer);
    producer(sc_module_name ins_name);
    virtual ~producer();
    void run();
 
public:
    simple_initiator_socket<producer> out;
    tlm_analysis_port<tlm_generic_payload> ap;
    sc_event done;
};
 
class Sender : public producer {
public:
    SC_HAS_PROCESS(Sender);
    Sender(sc_module_name instname);
    ~Sender();
    void SendData();
};
// sender.cpp
#include "sender.h"
 
#include <string>
 
producer::producer(sc_module_name ins_name) : sc_module(ins_name), out("out"), ap("ap") {
    SC_THREAD(run);
}
 
producer::~producer() = default;
 
void producer::run() {
    for (int i = 0; i < 10; ++i) {
        std::string data = "xxxxx: " + std::to_string(i);
        tlm_generic_payload gp;
        gp.set_data_ptr((uint8_t*)data.c_str());
        gp.set_command(TLM_WRITE_COMMAND);
        gp.set_address(rand());
        sc_time delay = sc_time(10, SC_NS);
        gp.set_data_length(data.length());
        out->b_transport(gp, delay);
        ap.write(gp);
        wait(20, SC_NS);
    }
    done.notify();
}
 
Sender::Sender(sc_module_name instname) : producer(instname) {
    SC_THREAD(SendData);
}
 
Sender::~Sender() = default;
 
void Sender::SendData() {
    uvmc_raise_objection("run");
    wait(done);
    uvmc_drop_objection("run");
}

SystemC 接收模块

// receiver.h
#pragma once
 
#include <string>
#include <iomanip>
#include <systemc.h>
#include <tlm.h>
using namespace tlm;
 
#include "simple_target_socket.h"
using tlm_utils::simple_target_socket;
 
class Receiver : public sc_module {
public:
    SC_HAS_PROCESS(Receiver);
    Receiver(sc_module_name ins_name);
    ~Receiver();
 
    virtual void b_transport(tlm_generic_payload &gp, sc_time &t);
 
public:
    simple_target_socket<Receiver> in;
    tlm_analysis_port<tlm_generic_payload> ap;
};
 
// receiver.cpp
#include "receiver.h"
 
Receiver::Receiver(sc_module_name ins_name) : sc_module(ins_name), in("in"), ap("ap") {
    in.register_b_transport(this, &Receiver::b_transport);
}
 
Receiver::~Receiver() = default;
 
void Receiver::b_transport(tlm_generic_payload &gp, sc_time &t) {
    unsigned char* data = gp.get_data_ptr();
    int len = gp.get_data_length();
    std::cout << "receive data: " << (char*)data << std::endl;
    ap.write(gp);
}

sc_main

// main.cpp
#include <systemc.h>
#include "receiver.h"
#include "sender.h"
 
int sc_main(int argc, char* argv[]) {
    Sender sender("sender");
    uvmc_connect(sender.out, "42");
    Receiver receiver("receiver");
    uvmc_connect(receiver.in, "foo");
    sc_start(1000, SC_NS);
    return 0;
}

System Verilog 数据回传模块

// sv_loop.sv
import uvm_pkg::*; 
import uvmc_pkg::*;
 
`include "uvm_macros.svh"
class producer extends uvm_component;
    uvm_tlm_b_initiator_socket #() out;
    uvm_analysis_port #(uvm_tlm_gp) ap;
    uvm_phase run_ph;
    `uvm_component_utils(producer)
    function new(string name, uvm_component parent=null);
        super.new(name,parent);
        out = new("out", this);
        ap = new("ap", this);
        run_ph = uvm_run_phase::get();
    endfunction
    task send(uvm_tlm_gp t, uvm_tlm_time delay);
        run_ph.raise_objection(this);
        out.b_transport(t, delay);
        ap.write(t);
        run_ph.drop_objection(this);
    /*
        uvm_tlm_gp gp = new;
        uvm_tlm_time delay = new("del",1e-12);
        run_ph.raise_objection(this);
        delay.set_abstime(10,1e-9);
        assert(gp.randomize() with { gp.m_byte_enable_length == 0;
                                                 gp.m_length inside {[1:8]};
                                                 gp.m_data.size() == m_length; } );
        `uvm_info("PRODUCER/PKT/SEND",{"\n",gp.sprint()},UVM_MEDIUM)
        out.b_transport(gp,delay);
        ap.write(gp);
        #100;
        `uvm_info("PRODUCER/END_TEST", "Dropping objection to ending the test",UVM_LOW)
        run_ph.drop_objection(this);
    */
    endtask
endclass
 
class consumer extends uvm_component;
    uvm_tlm_b_target_socket #(consumer) in;
    uvm_analysis_port #(uvm_tlm_generic_payload) ap;
    producer prod;
    `uvm_component_utils(consumer)
    function new(string name, uvm_component parent=null);
        super.new(name,parent);
        in = new("in",  this);
        ap = new("ap", this);
        prod = new("prod");
        uvmc_tlm #()::connect(prod.out, "foo");
    endfunction
 
    virtual task b_transport(uvm_tlm_gp t, uvm_tlm_time delay);
        // `uvm_info("CONSUMER/PKT/RECV",{"\n",t.sprint()},UVM_MEDIUM)
        // #(delay.get_realtime(1ns,1e-9));
        // delay.reset();
        // ap.write(t);
        // uvm_phase phase;
        prod.send(t, delay);
    endtask
endclass
 
module sv_main;
    consumer cons = new("cons");
    // producer prod = new("prod");
    initial begin
        uvmc_tlm #()::connect(cons.in, "42");
        // uvmc_tlm #()::connect(prod.out, "foo");
        run_test();
    end
endmodule

5、编译运行
make comp
./simv
注意如果编译在 uvmc_connect.cpp 263 行报错,提示错误如下
mbkk077x.png

那么需要将对应行代码修改为

cerr << "UVMC Error: Cannot open connections file '" << filename << "'" << endl;
打印输出
mbkk0b26.png

6、关键点
Systemc 和 System Verilog 的通信接口有点类似于 c++ 里面的 socket 通信一样,如果要通信,那么需要绑定到相同的一个端口上,这里的端口是一个字符串

// systemc
Sender sender("sender");
uvmc_connect(sender.out, "42");
Receiver receiver("receiver");
uvmc_connect(receiver.in, "foo");
// system verilog
consumer cons = new("cons");
uvmc_tlm #()::connect(cons.in, "42");
prod = new("prod");
uvmc_tlm #()::connect(prod.out, "foo");

对于 sv 接收,需要重写 b_transport 方法,并且需要定义输入 socket 接口

uvm_tlm_b_target_socket #(consumer) in;
uvm_analysis_port #(uvm_tlm_generic_payload) ap;
 
virtual task b_transport(uvm_tlm_gp t, uvm_tlm_time delay);
endtask
 对于 sv 发送,需要定义输出 socket 接口,并且调用 b_transport 进行发送

uvm_tlm_b_initiator_socket #() out;
uvm_analysis_port #(uvm_tlm_gp) ap;
 
run_ph.raise_objection(this);
out.b_transport(t, delay);
ap.write(t);
run_ph.drop_objection(this);

对于 sc 的接收端,需要定义输入 socket 接口,然后重写了 b_transport 方法接收数据

simple_target_socket<Receiver> in;
tlm_analysis_port<tlm_generic_payload> ap;

对于 sc 的发送端,需要定义输出 socket 接口,然后调用 b_transport 发送

simple_initiator_socket<producer> out;
tlm_analysis_port<tlm_generic_payload> ap;

附录: sc_module 在 verilog 中例化调用vcs 编译运行过程

(1)先编译systemc
syscan -full64 -sysc=dpi_if -cpp g++ -l syscan.log -tlm2 -cflags -DVCS ${C_SRC} -cflags "${C_DEFINE} ${INCLUDE_PATH}" $(pwd)/Components/TOP.cpp:TOP

其中C_SRC 在makefile 中定义:
C_SRC=$(shell find ./ -path "./csrc" -prune -o -path "./TEST" -prune -o -path "./Thirdparty" -prune -o -name '*TOP.cpp' -o -name 'main.cpp' -o -name '*.cpp' -print -o -name '*.c' -print -o -name '*.cc' -print | sed "s|^\.|$(pwd)|")
C_SRC+=${VIP_ROOT_PATH}/3pt_utils/uvmc/src/connect/sc/uvmc.cpp
INCLUDE_PATH += -I$(VCS_HOME)/etc/systemc/accellera_install/systemc233-gcc7/include/tlm_utils

(2)ln 到systemverilog 编译路径再编译systemverilog/verilog
ln -sTf ../../systemc/atomsimulator-main/csrc/sysc csrc/sysc
vcs $$XRUN_SYSC_FLIST $(COMP_OPTS) $(COMP_DEF) -f $(TBFILE) 2>&1 | tee cur_cmp.log

其中 XRUN_SYSC_FLIST="$(XRUN_SYSC_FLIST) -f ../systemc/atomsimulator-main/xrun.f"; \

最后运行和一般只有systemverilog/verilog 没有区别

./simv ${GUI}  $(RUN_OPTS) $$COV_OPTS -l ./log/$(tc)_$$seed.log -ucli -do ../cfg/vcs_ucli.cfg \
                +ntb_random_seed=$$seed +UVM_TESTNAME=$(tc) +WAVE_PATH=$(wave_path) +WAVE_MACRO=WAVE_$(wave_macro) +UVM_VERBOSITY=$(printlevel) $$VCS_FSDB_OPT $(FSDB_DBG_OTPS)

附录: sc_module 在 verilog 中例化调用xrun 编译运行过程
(1)生成文件列表,systemc可以和systemverilog 一起编译
echo "-DSC_INCLUDE_DYNAMIC_PROCESSES -DXRUN -Wcxx,"${C_DEFINE}" ${INCLUDE_PATH} ${C_SRC} $(pwd)/Components/TOP.cpp $(pwd)/Components/TOP.v" | sed "s/ /\n/g" > xrun.f

tlm 库路径:
INCLUDE_PATH += -I$(XCELIUM_HOME)/tools/systemc/include/tlm_utils
生成的文件列表 xrun.f 内容

mbkkiner.png

xrun 编译:

xrun $$XRUN_SYSC_FLIST $(COMP_OPTS) $(COMP_DEF) -f $(TBFILE) 2>&1 | tee cur_cmp.log 

其中:XRUN_SYSC_FLIST="$(XRUN_SYSC_FLIST) -f ../systemc/atomsimulator-main/xrun.f"; \

运行和一般只有systemverilog/verilog 没有区别

    xrun -R ${GUI}  $(RUN_OPTS) $$COV_OPTS -l ./log/$(tc)_$$seed.log -input ../cfg/xrun_ucli.cfg \
                -seed $$seed +ntb_random_seed=$$seed +WAVE_PATH=$(wave_path) +UVM_TESTNAME=$(tc) +WAVE_MACRO=WAVE_$(wave_macro) +UVM_VERBOSITY=$(printlevel) $(FSDB_DBG_OTPS); \

附录: verilog module在sc 中例化调用,vcs 编译过程(顶层为sc)
先编译verilog moudle 为 sc (就是转换为sc)

vlogan    -sysc -sc_model adder -sc_portmap the.map adder.v ${VLOGAN_EXTRA_SC}
vlogan    -sysc -sc_model multiplier -sc_portmap the.map multiplier.v ${VLOGAN_EXTRA_SC}

备注:VLOGAN_EXTRA_SC 可以为空
然后再运行 syscsim -sysc -timescale=1ps/1ps -debug_access+all stimulus.cpp display.cpp subtracter.cpp top.cpp main.cpp -l comp.log ${SYSCAN_EXTRA}

如果verilog/systemverilog 和 sc 互相嵌套,可以在sc 调用verilog/systemverilog 中,将 verilog/systemverilog编译为sc,然后顶层如果verilog/systemverilog, 参考sc_module 在 verilog 中例化调用vcs 编译运行过程例子,编译后再运行。

最后生成的fsdb 波形 可以显示systemc 时序描述
mbkkongj.png

这里u_TOP 就是systemc 顶层在verilog 中例化
verdi 还不能直接debug systemc 源码,还需要研究
mbkkq8u4.png

————————————————
部分来源于https://blog.csdn.net/yp18792574062/article/details/134375631

0

评论 (0)

取消