ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VCS 2018下UVMC混合仿真:SystemC与UVM互联

VCS 2018下UVMC混合仿真:SystemC与UVM互联 1. 为什么这组搭配这么香UVMC混合仿真的定位VCS 2018、UVMC、SystemC、SystemVerilog混合仿真这几个词凑在一起通常意味着一个很现实的场景你手里已经有一套SystemC写的参考模型或者算法团队交付的C模型而验证环境又必须是SystemVerilog/UVM两边需要连起来跑。如果靠DPI一个函数一个函数手工封装代码量大不说事务级的握手、端口映射、时间同步全都要自己处理。UVMC的作用就是把这些跨语言端口变成电话线在SystemC和SystemVerilog两侧各自调用一次uvmc_connect写上同一个字符串ID数据就能在两个世界之间流动起来。这篇文章我会基于VCS 2018带着你从一个16深FIFO模型开始把SystemC侧的参考模型、SystemVerilog侧的UVM driver、两端通过UVMC的互联、编译脚本、常见报错全部走一遍。完整代码都会放出来你照着搭一个最小工程就能跑通以后换成实际项目模型套路完全一样。1.1 混合仿真到底难在哪先回到最基础的问题SystemC和SystemVerilog各有各的优势谁也没法完全替代谁。SystemC适合做C/C算法级建模模型写出来和后续的C参考代码能直接复用SystemVerilog UVM则是目前芯片验证环境的主流sequence、factory、coverage这些机制非常成熟。于是验证过程中经常出现两边都要用的情况。但真正把它们放在同一个仿真器里跑问题就来了。第一是关键的时间同步问题。SystemC有自己的sc_event调度SystemVerilog也有自己的事件队列两边必须同时推进才能真正协同工作。第二是数据类型映射SystemC里是sc_uint8、unsigned intSV里是bit [7:0]、int事务字段要打包、拆包。第三是端口语义不匹配SystemC的sc_port、sc_export和UVM的uvm_analysis_port、uvm_blocking_put_port长得完全不一样直接连是连不上的。如果不用UVMC常规做法是写一堆DPI函数把SV侧的transaction手动拆成一个个C函数参数传过去。那一刻你会发现UVM里的sequence、sequencer这些漂亮的东西全都要绕开验证环境退化成了一堆函数调用事务级复用基本扯淡。1.2 UVMC到底解决了什么UVMC全称是UVM Connect它解决的问题就是把SystemC的TLM建模能力和SystemVerilog的UVM验证方法学连起来。它的核心思路是维护一个“统一端口命名空间”字符串ID是两端共同识别的标签。SystemC侧在sc_main里注册一个端口比如sc_fifo.putSV侧在UVM组件里用同样的字符串sc_fifo.put连接uvm_blocking_put_portUVMC框架会自动把SV的put()调用翻译成SystemC侧对应的写操作。这件事的价值不是省掉一个函数封装而是把两边从“手动按函数接缝”变成了“按端口对接”。SV侧仍然是sequence发送transactiondriver调用put_port.put(), 数据通过UVMC框架直接落到SystemC模型里。SystemC侧仍然是一个普通SC_MODULE不需要知道外面的验证环境长什么样。两边都只关心自己的接口耦合度低复用性就上来了。还有一个容易被忽略的点UVMC支持在SV侧定义transaction类然后把字段打包发给SystemC。这样SystemC模型看到的不再是一堆孤立参数而是一个完整的事务对象。对于FIFO、总线模型、算法模型这类有明确事务语义的组件这种方式非常自然。1.3 为什么必须是VCS 2018这个版本严格说UVMC在VCS 2014之后的版本里都能用但VCS 2018对SystemC 2.3和UVM-1.2的支持已经相当成熟-sysc选项、DPI-C编译、混合仿真的运行时库都稳定很多。我早期在VCS 2014上跑UVMC经常碰见SystemC版本不匹配的问题比如SV侧uvmc_connect编译通过链接时却报一串未定义的SC符号。到了VCS 2018配合UVMC 2.0这些问题明显少了很多。所以如果你手头的项目还停留在老版本VCS建议至少升级到2018再开始搭混合仿真环境。这跟追求新版本无关纯粹是省时间。2. 动手前的准备工作环境变量、UVMC库和工程目录开始写代码之前先把环境准备好。很多人在UVMC上翻车不是代码写错而是头文件路径、库路径、版本没配对。这里我按VCS 2018 UVMC 2.0的组合来演示。2.1 确认VCS版本与SystemC支持首先在终端里确认VCS版本vcs -ID你会看到类似vcs_mx_2018.09的输出。支持SystemC混合仿真关键是这一行里要有SystemC 2.3相关的字样或者直接看帮助vcs -help | grep -i sysc如果VCS安装正确能看到-sysc、-sysc...这一组选项。VCS编译SystemC模型时不是调独立的外部编译器链接而是把SystemC源码当成VCS的另一种输入文件和SystemVerilog一起编译成同一个simv可执行文件。这也是它比单纯用g自己编SystemC、再通过PLI接入要省事的地方。2.2 获取并配置UVMC库UVMC是Accellera家出的开源库可以直接从Accellera官网下载也可以在你VCS安装路径的packages目录里找通常叫uvmc-2.0之类。我习惯把它放到一个固定目录比如export UVMC_HOME/opt/uvmc/uvmc-2.0还需要确认SystemC库的位置。如果你和VCS配合的是安装自带的SystemC那SYSTEMC_HOME一般指向VCS目录下的systemc子目录。我这边习惯单独装一份SystemC 2.3.3export SYSTEMC_HOME/opt/systemc/systemc-2.3.3这两个环境变量最好写进.bashrc后面Makefile里都要用。另外运行simv时还需要找到动态库所以编译之后运行前要设置export LD_LIBRARY_PATH$UVMC_HOME/lib:$SYSTEMC_HOME/lib-linux64:$LD_LIBRARY_PATH2.3 规划工程目录和Makefile骨架我建议的最小工程目录长这样mix_prj/ sc/ fifo_model.h sc_main.cpp sv/ sc_trans.sv sc_driver.sv sc_env.sv sc_test.sv top.sv filelist.f Makefile目录结构不用太复杂但把SystemC和SystemVerilog分开是必须的否则文件一多编译命令会乱。Makefile我后面会给出完整版先留一个空壳all: simv simv: vcs ... clean: rm -rf simv csrc simv.daidir run.log在这套结构里SystemC模型是fifo_modelSV侧是标准的UVM driver/env/test最外层的top.sv负责调用run_test和dump波形。3. SystemC侧参考模型一个16深FIFO的完整实现参考模型我选择FIFO是因为它简单、直观又能覆盖put和get两个方向的事务传输。用一个真实的SystemC FIFO模型比写一个hello world更能让你理解UVMC的连接方式。3.1 用sc_fifo还是手写通道SystemC标准库里自带sc_fifo它有sc_fifo_in_if和sc_fifo_out_if两个接口分别处理写和读。直接用它有两个好处第一不用自己维护缓冲区满、空、锁存这些逻辑第二UVMC自带对sc_fifo接口的适配SV侧的put_port、get_port可以直接对接省去自定义TLM socket的麻烦。如果你想在项目里模拟更真实的总线协议也可以把手写simple_target_socket但第一步先用sc_fifo跑通链路是最稳妥的方式。3.2 FIFO模型的SystemC代码在sc/fifo_model.h里代码如下#ifndef FIFO_MODEL_H #define FIFO_MODEL_H #include systemc.h #include uvmc.h class fifo_model : public sc_module { public: // 写入口SV侧通过 put 端口向该model写入数据 sc_exportsc_fifo_in_ifunsigned int put_export; // 读出口SV侧通过 get 端口从该model读取数据 sc_exportsc_fifo_out_ifunsigned int get_export; SC_HAS_PROCESS(fifo_model); fifo_model(sc_module_name name_) : sc_module(name_), put_export(put_export), get_export(get_export), m_fifo(m_fifo, 16) { // 两个export绑定到同一个sc_fifo通道 put_export.bind(m_fifo); get_export.bind(m_fifo); } private: sc_fifounsigned int m_fifo; }; #endif这段代码虽然短但值得说一下设计意图。sc_exportsc_fifo_in_ifunsigned int表示这个模块对外提供了一个“写FIFO”的能力SV侧可以把它理解成写通道sc_exportsc_fifo_out_ifunsigned int表示“读FIFO”的能力对应读通道。两者绑到同一个sc_fifo对象上于是写进FIFO的数据读通道就能取出来。3.3 在sc_main中把端口注册到UVMC名字服务SystemC侧不能光有端口还要让UVMC在运行时找到这些端口。在sc/sc_main.cpp里写#include systemc.h #include fifo_model.h #include uvmc.h int sc_main(int argc, char* argv[]) { fifo_model model(model); // 注册两个端口到UVMC统一名字空间 uvmc_connect(model.put_export, sc_fifo.put); uvmc_connect(model.get_export, sc_fifo.get); sc_start(); return 0; }这里uvmc_connect做的事情相当于给每个端口贴了一个标签。标签字符串不需要和C变量名一致只要保证SV侧用的标签相同就行。我习惯用模块名.端口名的规范来命名方便在多个SystemC模型时避免冲突。如果未来你的SystemC模型有多个实例字符串ID一定要不同不然UVMC会把两侧端口映射错。这个坑我踩过一次两个模型分别连了同名标签仿真时数据全跑到了一个模型里。4. SystemVerilog侧UVM环境与UVMC互联SystemC侧准备好的同时SV侧也要有对应的UVM端口。UVMC连接两侧的机制是SV侧某个uvm_port用一个字符串ID注册SC侧某个sc_port/sc_export用同样字符串ID注册两端就被框架拉在一起。4.1 Transaction定义与端口声明先定义事务类。我这里为了演示只放了一个8bit数据字段data你可以根据自己总线协议扩展成地址、长度、校验等字段。// sv/sc_trans.sv class sc_trans extends uvm_sequence_item; rand bit [7:0] data; uvm_object_utils_begin(sc_trans) uvm_field_int(data, UVM_ALL_ON) uvm_object_utils_end function new(string name sc_trans); super.new(name); endfunction endclass接下来是UVM driver。driver里有两种端口put_port和get_port分别对应SystemC侧的写FIFO和读FIFO。// sv/sc_driver.sv class sc_driver extends uvm_driver #(sc_trans); uvm_component_utils(sc_driver) uvm_blocking_put_port #(sc_trans) put_port; uvm_blocking_get_port #(sc_trans) get_port; function new(string name sc_driver, uvm_component parent null); super.new(name, parent); put_port new(put_port, this); get_port new(get_port, this); endfunction virtual task run_phase(uvm_phase phase); sc_trans req; sc_trans wr; sc_trans rd; forever begin seq_item_port.get_next_item(req); // 把sequence发来的事务复制一份避免覆盖原对象 wr sc_trans::type_id::create(wr); wr.copy(req); // 写SystemC FIFO put_port.put(wr); // 从SystemC FIFO读回 rd sc_trans::type_id::create(rd); get_port.get(rd); if (rd.data ! wr.data) uvm_error(SC_DRV, $sformatf(data mismatch: exp %0h got %0h, wr.data, rd.data)) else uvm_info(SC_DRV, $sformatf(pass: %0h, rd.data), UVM_LOW) seq_item_port.item_done(); end endtask endclass这里的逻辑比较直观sequence给一个transactiondriver先写入SC的FIFO再从SC的FIFO读出来比较是否一致。因为是同一个FIFO所以读回来的数据应该和写进去一致。这个测试虽然简单但能同时验证两条路径的UVMC映射只要有一点连接错误仿真立刻就会报mismatch。4.2 Driver和Env的完整代码再搭一个标准UVM env把driver和sequencer接起来。// sv/sc_env.sv class sc_env extends uvm_env; uvm_component_utils(sc_env) sc_driver drv; uvm_sequencer #(sc_trans) sqr; function new(string name sc_env, uvm_component parent null); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); sqr uvm_sequencer#(sc_trans)::type_id::create(sqr, this); drv sc_driver::type_id::create(drv, this); endfunction function void connect_phase(uvm_phase phase); super.connect_phase(phase); drv.seq_item_port.connect(sqr.seq_item_export); endfunction endclass4.3 通过uvmc_connect完成跨语言绑定在test里完成两端绑定。绑定动作放在build_phase里因为此时env.drv已经创建完毕。// sv/sc_test.sv class sc_test extends uvm_test; uvm_component_utils(sc_test) sc_env env; function new(string name sc_test, uvm_component parent null); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); env sc_env::type_id::create(env, this); // UVMC: SV侧端口映射到SystemC侧端口 uvmc_connect(env.drv.put_port, sc_fifo.put); uvmc_connect(env.drv.get_port, sc_fifo.get); endfunction task run_phase(uvm_phase phase); sc_trans item; phase.raise_objection(this); item sc_trans::type_id::create(item); item.data 8hA5; env.sqr.start_item(item); env.sqr.finish_item(item); #10; phase.drop_objection(this); endtask endclass最外层的top.sv// sv/top.sv module top; import uvm_pkg::*; import uvmc_pkg::*; initial begin run_test(sc_test); end initial begin $fsdbDumpfile(mix.fsdb); $fsdbDumpvars(0, top); end endmodule这里的uvmc_pkg是UVMC库附带的SystemVerilog包编译时需要通过filelist引入。connect的字符串sc_fifo.put和sc_fifo.get与SystemC侧sc_main里注册的字符串完全一致。只要有一处拼写不同仿真时数据没反应排查起来很费劲。5. 编译运行VCS 2018下的完整命令与Makefile实战代码都齐了接下来就是把它们编译成一个simv然后跑起来。这一步最容易出问题但看清每个参数的意思之后就不难了。5.1 编译命令的每个参数到底在干嘛先给一个可以直接用的Makefile# Makefile UVMC_HOME ? /opt/uvmc/uvmc-2.0 SYSTEMC_HOME ? /opt/systemc/systemc-2.3.3 VCS vcs VCS_OPTS -sverilog \ -ntb_opts uvm \ -sysc \ -debug_accessall \ -CFLAGS -I$(SYSTEMC_HOME)/include -I$(UVMC_HOME)/include \ -LDFLAGS -L$(SYSTEMC_HOME)/lib-linux64 -Wl,-rpath,$(SYSTEMC_HOME)/lib-linux64 all: simv simv: filelist.f $(VCS) $(VCS_OPTS) -f filelist.f -o simv run: simv ./simv UVM_TESTNAMEsc_test -l run.log wave: simv ./simv UVM_TESTNAMEsc_test -l run.log -gui clean: rm -rf simv csrc simv.daidir ucli.key run.logfilelist.f内容如下$UVMC_HOME/src/uvmc_pkg.sv $UVMC_HOME/src/uvmc.sv sv/sc_trans.sv sv/sc_driver.sv sv/sc_env.sv sv/sc_test.sv sv/top.sv sc/fifo_model.h sc/fifo_model.cpp sc/sc_main.cpp注意filelist里我写了sc/fifo_model.cpp但在前面只给出了头文件。实际使用时你需要新建一个空实现文件或者直接把fifo_model放在头文件里然后在filelist中去掉cpp行。我更推荐用头文件加sc_main.cpp的方式在sc_main.cpp里#include fifo_model.h这样少一个源文件$UVMC_HOME/src/uvmc_pkg.sv $UVMC_HOME/src/uvmc.sv sv/sc_trans.sv sv/sc_driver.sv sv/sc_env.sv sv/sc_test.sv sv/top.sv sc/sc_main.cpp下面解释几个关键参数-sverilog打开SystemVerilog语法支持必须加。-ntb_opts uvm让VCS加载正确版本的UVM库同时保证uvm_pkg可用。-sysc这一项是VCS进入SystemC协同仿真的钥匙。不加的话.cpp文件里的sc_module完全不会被识别。-CFLAGS传给C编译器的头文件路径。必须同时包含SystemC和UVMC的头文件路径。-LDFLAGS链接时需要的SystemC库路径。用-Wl,-rpath把路径写进可执行文件这样运行时不用手动设LD_LIBRARY_PATH也能找到libsystemc。-f filelist.f把所有源文件打包在一个文件列表里比在命令行上一长串参数清爽。5.2 跑仿真、看日志、抓波形编译完成后直接运行make run你会在终端里看到UVM banner然后是driver打印的pass: a5。如果走到这一步说明SystemC侧和SystemVerilog侧的UVMC连接已经通了transaction成功穿过语言边界。抓波形时用make wave或者在运行命令里加-gui打开Verdi看波形。top.sv里已经加了$fsdbDumpfile和$fsdbDumpvars所以仿真结束会在当前目录生成mix.fsdb./simv UVM_TESTNAMEsc_test -l run.log打开波形verdi -f filelist.f -ssf mix.fsdb 不过要提醒一句SystemC内部的信号默认不在FSDB里。如果想把SystemC的内部信号也dump出来需要在SystemC侧额外做配置这一点不同VCS版本处理方式不一样通常会涉及sc_elab_and_sim或-sysc的dump选项。第一次跑通SV侧波形就够用了。5.3 常见报错与排查速查表混合仿真的报错通常集中在编译和链接两个阶段下面是几张我在实际项目中反复用到的排查表。报错信息原因解决办法Cannot find systemc.hCFLAGS没包含SystemC路径检查SYSTEMC_HOME/include路径是否设置并在-CFLAGS里显式加-ICannot find pkg_uvmcUVMC的SV包没被编译确认filelist里有uvmc_pkg.sv和uvmc.svundefined reference to sc_core::sc_simcontext链接阶段没找到SystemC库检查-LDFLAGS里SystemC库路径确认libsystemc.a/so存在uvmc_connect is not definedUVMC头文件或SV包没引入SystemC侧检查#include uvmc.hSV侧检查import uvmc_pkg::*;仿真跑完没有数据流动字符串ID两侧不一致对比SC侧sc_main和SV侧build_phase中uvmc_connect的字符串参数timeout waiting for SystemCsc_start和仿真器时间同步异常把sc_main里的sc_start()改成sc_start(1, SC_NS)并加循环或检查VCS SystemC版本这里特别说一下sc_start()。在VCS混合仿真里我建议的写法是while (true) { sc_start(1, SC_NS); }这种写法让SystemC侧不断以1ns粒度推进配合VCS事件调度。直接裸写sc_start()在某些版本里会一直卡住因为SystemC没有独立的事件去触发退出。6. 如果还想再进一步我的几点实操心得整个UVMC混合仿真环境跑通之后你会发现它其实不难难的是遇到问题时的排查思路。最后分享几个我在实际工程里的经验。6.1 传输粒度别定太细UVMC最舒服的用法是传输事务级对象比如总线请求、FIFO数据包。我见过有人把单个信号通过UVMC传比如每次传一位enable结果跨语言开销非常大仿真慢得没法忍受。混合仿真更适合把SystemC模型当成一个粗粒度组件SV侧用transaction和它交互而不是把底层信号级握手拖进SystemC。如果你的SystemC模型必须和SV逐拍交互尽量在SystemC内部把若干拍行为封装成一个函数或一个TLM事务把交互粒度提升到事务级性能会好很多。6.2 时间同步和sc_start的坑时间同步的问题建议在一开始就规划好。SystemC侧的进程如果写了无限循环仿真可能在启动阶段就卡住。我一般是把所有SystemC进程设计成“事件驱动、被动响应”没有新事务就停在等待状态。这样sc_start循环既不会空转也不会导致SV侧timeout。另外当你在SV侧发完一个transaction后最好加一个#10之类的延时给SystemC侧调度器留出反应时间。虽然UVMC底层会处理跨语言同步但在VCS 2018上实测适当加一拍延时能让整个仿真更稳尤其在多个事务连续发送时。6.3 后续扩展方向这个最小工程跑通后可以往几个方向扩展。一是把SystemC模型从FIFO换成真实的总线模型比如AXI或APB在SystemC里用simple_target_socket实现事务处理SV侧继续用UVM sequence发送总线请求。二是增加多个SystemC模型只要给每个模型的端口分配唯一字符串ID就行。三是接入功能覆盖率SV侧正常做covergroupSystemC模型返回的数据可以通过uvmc_pack映射到SV transaction上。UVMC的好处就在这它给了两端一个稳定的“连接层”让SystemC和SystemVerilog各干各擅长的部分互不绑架。把这些基础跑通后面无论是算法模型验证、C模型替换RTL还是软硬件协同仿真你的验证平台都能稳稳接住。
返回列表