前言
本篇将演示一个UVM-SV通过C/CPP作为胶合语言,调用Matlab的示例;并介绍两种方式,一种DPI
封装为函数的方式,一种使用UVM-ML OA
库,基于TLM通信的方式。
调用Matlab
Matlab是一款用于数据分析,算法开发和模型创建的软件;其提供众多的工具箱及交互式的操作,方便算法的开发。当使用Matalb作为RTL的算法model时,如何调用Matalb作为参考模型,是验证人员不得不解决的一点。
总结如下几种方式:
-
直接启动运行matlab程序,通过读写文件完成model的输入输出
输入数据从文件中读取,载入到matlab,运行完成后再写到输出文件中;验证平台的激励也来自输入文件,RTL输出的数据写入到输出文件;对比matlab和RTL的输出文件; -
C/C++调用Matlab引擎
engine.h
中提供函数engEvalString
,使得C/C++可直接调用Matlab引擎,运行.m
文件;这种方式需要完整的安装Matlab程序,获取Matlab license,相当于后台运行Matlab。🔗Call MATLAB Functions from C++ -
将
.m
文件编译为.so
,利用runtime库实现.so
的调用
利用mcc
(Matlab Compiler)将.m
文件编译为.so
文件,C/CPP通过调用对应API完成和.so
的数据交互;因为这种方式只依赖于Matlab Runtime(MCR
)共享库,有很好的移植性,运行程序不需要安装完整的Matlab。
CPP调用的API又分为三种:- 纯C风格的API
- 支持C++03编程的API,
mwArray API
,Matlab_R2009引入;🔗Deploy to C++ Applications Using mwArray API - 支持C++11编程的API,
MATLAB Data API
,Matlab_R2018引入;🔗Deploy to C++ Applications Using MATLAB Data API
4. 将.m
文件转成.c
文件
Matlab的dpigen
命令支持将.m
文件直接转化成.c
文件,并提供相关DPI调用函数;但是.m
转.c
对matlab的编写有一定要求,转化后的.c
可读性不到,debug困难。🔗DPI Generation for MATLAB Code
综合对比上述几种方式,建议采用第三种;本篇将通过第三种方式的mwArray API
,调用Matlab作为参考模型。
本篇演示的Matlab算法是RGB转YCvCr色域,效果如下图:
RGB888_to_YCbCr444.m
文件:
function [Y_o,Cb_o,Cr_o]=RGB888_to_YCbCr444(R_i,G_i,B_i,cfg_struct)
%[height,width] = size(R_i);
height = cfg_struct.height;
width = cfg_struct.width;
img_o = zeros(height,width,3);
img_i = double(cat(3,R_i,G_i,B_i));
for i = 1 : height
for j= 1 : width
img_o(i,j,1) = bitshift(( img_i(i,j,1)*76 + img_i(i,j,2)*150 + img_i(i,j,3)*29),-8);
img_o(i,j,2) = bitshift((-img_i(i,j,1)*43 - img_i(i,j,2)*84 + img_i(i,j,3)*128 + 32768),-8);
img_o(i,j,3) = bitshift(( img_i(i,j,1)*128 - img_i(i,j,2)*107 - img_i(i,j,3)*20 + 32768),-8);
end
end
Y_o = img_o(:,:,1);
Cb_o = img_o(:,:,2);
Cr_o = img_o(:,:,3);
end
RTL代码来自🔗crazybingo_VIP_RGB888_YCbCr444.v
Matlab Compiler的mcc
命令将.m
文件转为.so
:
${MATLAB_HOME}/bin/mcc -W cpplib:libRGB888_to_YCbCr444 RGB888_to_YCbCr444.m -g -d output/
生成内容如下:
libRGB888_to_YCbCr444.h
中包含可供C/CPP调用的API。
UVM_DPI_Matlab
DPI调用libRGB888_to_YCbCr444.h
中的API,需要利用🔗mwArray API进行数据结构的转化;mwArray
对应的头文件mclmcrrt.h,mclcppclass.h
,runtime共享库libmwmclmcrrt.so
;
编译动态库命令:
CUR_DIR_PATH := $(shell echo $(CURDIR))
all: matlab_gen gen_lib
matlab_gen:
${MATLAB_HOME}/bin/mcc -W cpplib:libRGB888_to_YCbCr444 RGB888_to_YCbCr444.m -g -d output/
gen_lib:
g++ -shared -fPIC -g -Wno-write-strings \
-Ioutput -I${VCS_HOME}/include -Isrc_cpp -I${MATLAB_HOME}/extern/include \
-lmwmclmcrrt -L${MATLAB_HOME}/runtime/glnxa64 \
-lRGB888_to_YCbCr444 -Loutput \
-Wl,-rpath=${CUR_DIR_PATH} -Wl,-rpath=${CUR_DIR_PATH}/output \
./src_cpp/RGB888_to_YCbCr444_common.cpp \
./src_cpp/RGB888_to_YCbCr444_dpi.cpp \
-o libRGB888_to_YCbCr444_sim.so
clean:
-rm -rf output *.so
SV DPI调用Matalb的数据传递路径: SV -> DPI-C -> CPP -> mwArray API -> libRGB888_to_YCbCr444 API
ref_model中调用DPI,计算结果:
`ifndef MY_MODEL__SV
`define MY_MODEL__SV
import "DPI-C" function chandle DPI_RGB888_to_YCbCr444_initialize(input chandle existhandle);
import "DPI-C" function void DPI_RGB888_to_YCbCr444_terminate(input chandle existhandle);
import "DPI-C" function void DPI_RGB888_to_YCbCr444(input chandle existhandle,
output int unsigned Y_dat_o[],
output int unsigned Cb_dat_o[],
output int unsigned Cr_dat_o[],
input int unsigned R_dat_o[],
input int unsigned G_dat_o[],
input int unsigned B_dat_o[],
input my_ctrl_s my_ctrl_struct
);
class my_model extends uvm_component;
uvm_blocking_get_port #(my_transaction) port;
uvm_analysis_port #(my_transaction) ap;
extern function new(string name, uvm_component parent);
extern function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
`uvm_component_utils(my_model)
endclass
function my_model::new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void my_model::build_phase(uvm_phase phase);
super.build_phase(phase);
port = new("port", this);
ap = new("ap", this);
endfunction
task my_model::main_phase(uvm_phase phase);
chandle dpi_ch;
my_transaction tr;
my_transaction new_tr;
super.main_phase(phase);
dpi_ch = DPI_RGB888_to_YCbCr444_initialize(null);
while(1) begin
port.get(tr);
new_tr = new("new_tr");
new_tr.copy(tr);
#10ns;
DPI_RGB888_to_YCbCr444(dpi_ch,
new_tr.dat1,
new_tr.dat2,
new_tr.dat3,
tr.dat1,
tr.dat2,
tr.dat3,
tr.my_ctrl_struct);
`uvm_info("my_model", "call matlab gen new tr:", UVM_LOW)
new_tr.print();
ap.write(new_tr);
end
endtask
`endif
示例的完整代码可参考:🔗ML_verify_share
UVM_ML_Matlab
本节使用的UVM_ML-OA
库介绍参考🔗multi-language verification (四)SystemC Verification、SystemC-SystemVeilog混仿;
UVM_ML-OA安装
tar -zxvf UVM_ML-1.13.0.tar.gz ; cd UVM_ML-1.13.0/ml
source compile_vcs_lib.csh
生成动态库
#!bin/tcsh -f
setenv UVM_ML_HOME /home/holden/systemc_work/lib/UVM_ML-1.13.0
setenv OSCI_INSTALL /home/holden/systemc_work/lib/systemc-2.3.3
setenv SYSTEMC_HOME $OSCI_INSTALL
setenv OSCI_SRC $OSCI_INSTALL
setenv UVM_ML_CC g++
setenv UVM_ML_LD g++
setenv UVM_ML_SVDPI_DIR $VCS_HOME/include
setenv GET_OSCI_VERSION_EXTRA_FLAGS ""
source ./install_vcs.csh --clean --64bit --no-specman
运行完成后,打印出如下log:
***********************************
*
* Setup
* using compiler version 9.2
* - for 64 bit
* - with debug information
* - with cleaning
* - with OSCI UVM-SC framework and adapter for OSCI 2.3.3
* - without IES NCSC UVM-SC framework and adapter
* - without Specman UVM-E adapter
* completed successfully
*
* The information about the environment variables
* vital for running UVM-ML
* may be found in /home/holden/systemc_work/lib/UVM_ML-1.13.0/ml/setup_64.csh (CSH format),
* in /home/holden/systemc_work/lib/UVM_ML-1.13.0/ml/setup_64.sh (Bourne shell format)
* and in /home/holden/systemc_work/lib/UVM_ML-1.13.0/ml/setup_64.mk (Makefile format)
***********************************
find
查找确认生成以下动态库:
holden@localhost.localdomain:/home/holden/systemc_work/lib/UVM_ML-1.13.0/ml
>find . -name "*.so"
./libs/backplane/9.2/64bit/libuvm_ml_bp.so
./libs/osci/2.3.3/9.2/64bit/libuvm_sc_fw_osci.so
./libs/osci/2.3.3/9.2/64bit/libuvm_sc_ml_osci.so
./libs/osci/2.3.3/9.2/64bit/libuvm_osci.so
./libs/uvm_sv/9.2/64bit/libuvm_sv_ml.so
cd examples/features/tlm1/sc_sv/sv_with_sc_ref_model
make all
运行sv_with_sc_ref_model
exmaple,这是一个sv TLM1调用sc当作ref_model的demo。
UVM_ML_HOME = /home/holden/systemc_work/lib/UVM_ML-1.13.0
SYSTEMC_HOME = /home/holden/systemc_work/lib/systemc-2.3.3
all: clean create compile run
create:
g++ -g -fPIC -o liball_osci_vcs.64.so \
${UVM_ML_HOME}/ml/examples/features/tlm1/sc_sv/sv_with_sc_ref_model/sctop.cpp \
-D_GLIBCXX_USE_CXX11_ABI=1 \
-Xlinker -rpath -Xlinker ${UVM_ML_HOME}/ml/libs/osci/2.3.3/9.2/64bit \
-Xlinker -Bsymbolic -L${UVM_ML_HOME}/ml/libs/osci/2.3.3/9.2/64bit \
-luvm_sc_fw_osci -luvm_sc_ml_osci \
-shared -I${SYSTEMC_HOME}/include -I${UVM_ML_HOME}/ml/adapters/uvm_sc \
-I${UVM_ML_HOME}/ml/adapters/uvm_sc/osci -I${UVM_ML_HOME}/ml/frameworks/uvm/sc \
-I${UVM_ML_HOME}/ml/examples/features/tlm1/sc_sv/sv_with_sc_ref_model
compile:
vcs -o simv64 -top topmodule \
-debug_access+all -kdb -lca -sverilog -timescale=1ns/1ns \
-l comp.log -full64 \
+define+USE_UVM_ML_RUN_TEST \
-CFLAGS -DVCS \
${UVM_ML_HOME}/ml/frameworks/uvm/sv/1.2-ml/src/dpi/uvm_dpi.cc \
${UVM_ML_HOME}/ml/frameworks/uvm/sv/1.2-ml/src/uvm_pkg.sv \
+incdir+${UVM_ML_HOME}/ml/frameworks/uvm/sv/1.2-ml/src \
+incdir+${UVM_ML_HOME}/ml/frameworks/uvm/sv/1.2-ml/src/dpi \
+incdir+${UVM_ML_HOME}/ml/examples/features/tlm1/sc_sv/sv_with_sc_ref_model \
+incdir+${UVM_ML_HOME}/ml/adapters/uvm_sv \
${UVM_ML_HOME}/ml/adapters/uvm_sv/uvm_ml_adapter.sv \
${UVM_ML_HOME}/ml/examples/features/tlm1/sc_sv/sv_with_sc_ref_model/test.sv
run:
./simv64 -l run.log \
-sv_liblist ./bootstrap_file
clean:
-rm -rf simv* csrc *.log *.so
RGB888_to_YCbCr444 UVM_ML-OA示例
SC侧的transaction:
do_pack(), do_unpack()
函数分别对bit流打包、解包
//my_transaction.h
#ifndef MY_TRANSACTION_H
#define MY_TRANSACTION_H
#include <systemc.h>
#include <uvm.h>
using namespace uvm;
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using std::string;
using std::vector;
#include "RGB888_to_YCbCr444_common.h"
#define TIME_STRING sc_time_stamp().to_string().c_str()
class my_transaction : public uvm_object {
public:
UVM_OBJECT_UTILS(my_transaction)
cfg_struct cfg_s;
std::vector<unsigned int> dat1;
std::vector<unsigned int> dat2;
std::vector<unsigned int> dat3;
// public methods defined for UVM
public:
my_transaction() {}
virtual ~my_transaction() {}
virtual void do_print(std::ostream&) const;
virtual void do_pack(uvm::uvm_packer&) const;
virtual void do_unpack(uvm::uvm_packer&);
virtual void do_copy(const uvm::uvm_object*);
virtual bool do_compare(const uvm::uvm_object*) const;
}; // end of class my_transaction
inline bool operator == (const my_transaction & arg1, const my_transaction & arg2) {
return arg1.do_compare(&arg2);
}
inline bool operator != (const my_transaction & arg1, const my_transaction & arg2) {
return !(arg1 == arg2);
}
UVM_OBJECT_REGISTER(my_transaction)
#endif
//my_transaction.cpp
#include "my_transaction.h"
#include <iostream>
#include <algorithm>
void my_transaction::do_print(std::ostream& os) const {
os << "{ ";
os << "cfg_s.height = ";
os << cfg_s.height << " ";
os << "cfg_s.width = ";
os << cfg_s.width << " ";
os << " }";
os << endl;
}
void my_transaction::do_pack(uvm::uvm_packer& packer) const {
packer << cfg_s.height;
packer << cfg_s.width;
packer << dat1;
packer << dat2;
packer << dat3;
}
void my_transaction::do_unpack(uvm::uvm_packer& packer) {
packer >> cfg_s.height;
packer >> cfg_s.width;
packer >> dat1;
packer >> dat2;
packer >> dat3;
}
void my_transaction::do_copy(const uvm::uvm_object* rhs) {
const my_transaction* drhs = dynamic_cast<const my_transaction*>(rhs);
if (!drhs) { cerr << "ERROR in do_copy" ; return ; }
this->cfg_s.height = drhs->cfg_s.height;
this->cfg_s.width = drhs->cfg_s.width;
this->dat1 = drhs->dat1;
this->dat2 = drhs->dat2;
this->dat3 = drhs->dat3;
}
bool my_transaction::do_compare(const uvm::uvm_object* rhs) const {
const my_transaction* drhs = dynamic_cast<const my_transaction*>(rhs);
if (!drhs) { cerr << "ERROR in do_compare" ; return false ; }
if (this->cfg_s.height != drhs->cfg_s.height) return false;
if (this->cfg_s.width != drhs->cfg_s.width) return false;
if (this->dat1 != drhs->dat1) return false;
if (this->dat2 != drhs->dat2) return false;
if (this->dat3 != drhs->dat3) return false;
return true;
}
SV侧的transaction:
pack(),unpack()
函数来自uvm_object_utils_begin
宏注册的函数;
//my_transaction.sv
`ifndef MY_TRANSACTION__SV
`define MY_TRANSACTION__SV
typedef struct {
rand int unsigned frame_height;
rand int unsigned frame_width;
} my_ctrl_s;
class my_transaction extends uvm_sequence_item;
rand int unsigned dat1[];
rand int unsigned dat2[];
rand int unsigned dat3[];
rand my_ctrl_s my_ctrl_struct;
constraint my_transaction_c {
my_ctrl_struct.frame_width inside {[200:300]};
my_ctrl_struct.frame_height inside {[100:200]};
dat1.size == my_ctrl_struct.frame_width*my_ctrl_struct.frame_height;
dat2.size == my_ctrl_struct.frame_width*my_ctrl_struct.frame_height;
dat3.size == my_ctrl_struct.frame_width*my_ctrl_struct.frame_height;
foreach(dat1[i]){
dat1[i] inside {[0:8'hFF]};
dat2[i] inside {[0:8'hFF]};
dat3[i] inside {[0:8'hFF]};
}
}
`uvm_object_utils_begin(my_transaction)
`uvm_field_int (my_ctrl_struct.frame_height,UVM_ALL_ON | UVM_NOCOMPARE)
`uvm_field_int (my_ctrl_struct.frame_width,UVM_ALL_ON | UVM_NOCOMPARE)
`uvm_field_array_int (dat1,UVM_ALL_ON)
`uvm_field_array_int (dat2,UVM_ALL_ON)
`uvm_field_array_int (dat3,UVM_ALL_ON)
`uvm_object_utils_end
function new(string name = "my_transaction");
super.new();
endfunction
endclass
`endif
ref_model.cpp
中调用libRGB888_to_YCbCr444 API:
class ref_model : public uvm_component,
tlm_analysis_port<my_transaction>
{
public:
sc_port<tlm_analysis_if<my_transaction>, 2, SC_ONE_OR_MORE_BOUND> sc_ap;
sc_export<tlm_analysis_if<my_transaction> > sc_imp;
ref_model(sc_module_name nm) : uvm_component(nm)
, sc_ap("sc_ap")
, sc_imp("sc_imp")
{
sc_imp(*this);
}
UVM_COMPONENT_UTILS(ref_model)
void build() {
io_printf("[SC] %s: ref_model::build\n",TIME_STRING);
}
void write(const my_transaction& tr) {
my_transaction out_tr;
unsigned int frm_size;
unsigned int *dat1_h;
unsigned int *dat2_h;
unsigned int *dat3_h;
frm_size = tr.cfg_s.height*tr.cfg_s.width;
dat1_h = (unsigned int*)malloc(sizeof(unsigned int)*frm_size);
dat2_h = (unsigned int*)malloc(sizeof(unsigned int)*frm_size);
dat3_h = (unsigned int*)malloc(sizeof(unsigned int)*frm_size);
// call matlab API
io_printf("[SC] %s: ref_model::before call matlab\n",TIME_STRING);
init_RGB888_to_YCbCr444();
RGB888_to_YCbCr444_main_process(
dat1_h,
dat2_h,
dat3_h,
&(tr.dat1[0]),
&(tr.dat2[0]),
&(tr.dat3[0]),
&tr.cfg_s
);
io_printf("[SC] %s: ref_model::after call matlab\n",TIME_STRING);
// construct out transaction
out_tr.cfg_s.height = 0; // no use
out_tr.cfg_s.width = 0; // no use
out_tr.dat1 = std::vector<unsigned int>(dat1_h,(dat1_h+frm_size));
out_tr.dat2 = std::vector<unsigned int>(dat2_h,(dat2_h+frm_size));
out_tr.dat3 = std::vector<unsigned int>(dat3_h,(dat3_h+frm_size));
sc_ap->write(out_tr);
io_printf("[SC] %s: ref_model::after send out transaction\n",TIME_STRING);
free(dat1_h);
free(dat2_h);
free(dat3_h);
out_tr.dat1.clear();
out_tr.dat2.clear();
out_tr.dat3.clear();
}
};
UVM_COMPONENT_REGISTER(ref_model)
my_env::connech_phase
实现SC和SV的TLM连接:
function void my_env::connect_phase(uvm_phase phase);
super.connect_phase(phase);
.....
void'(uvm_ml::connect(mdl.sc_ap.get_full_name(),{get_full_name(),".sctop.model.sc_imp"}));
void'(uvm_ml::connect({get_full_name(),".sctop.model.sc_ap"},mdl.sc_imp.get_full_name()));
endfunction
SV UVM_ML-OA调用Matalb的数据传递路径:SV-> SV Port-> SC Export-> CPP -> mwArray API -> libRGB888_to_YCbCr444 API
结果返回路径则相反。
示例的完整代码可参考:🔗ML_verify_share