VCS与Verdi的联合仿真

时间:2022-07-22
本文章向大家介绍VCS与Verdi的联合仿真,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

一、Verdi用途与优势

Verdi主要用于生成fsdb模型,同VCS使用的vcd文件相比,verdi使用的fsdb相当于vcd文件经过霍夫编码压缩之后的精简版,可用于查看fsdb波形并追踪RTL代码。

虽说verdi、modelsim都是用来调试波形, modelsim与verdi相比,最大的缺点是波形不会全dump,wave窗口拉不全的话需要重新跑,而verdi边运行边查看。具体做法是,在使用tcl指令,在运行仿真时,设置仿真时间,每次run完毕之后,在nWave窗口中file——>自动加载(shift+L快捷键),异常方便!

二、FSDB波形

Verdi只能查看fsdb格式的波形,而VCS可以生成供DVE查看的vpd格式的波形,如果想要输出fsdb格式的波形,需要额外在testbench中添加fsdb指令,或者通过脚本进行设置。FSDB全称为(Fast Signal Data Base):快速信号数据库,fsdb文件是Verdi使用的一种专用数据格式,fsdb通过verilog的PLI接口实现,如:

fsdbDumpfile("f0.fsdb"); //指定生成的fsdb文件的文件名
fsdbDumpars(0,top);  //指定dump的变量

三、开发环境与配置

使用Verdi前需要对开发环境进行配置,使得系统在调用Verdi时找到其路径,在使用verdi时找到其库文件与路径,所以要注意三个变量和三条指令的使用:Verdi_HOME/NOVAS_HOME 仿真器默认,为设置PATH做准备 PATH 让系统能够找到verdi,如果不设置PATH,在调用Veidi时系统无法识别。

LD_LIBRARY_PATH 为了使得Verdi在运行过程中找到需要的库文件,对Library的位置在开发环境中进行配置,可通过:

echo $LD_LIBRARY_PATH | sed  ‘s/:/n/g’

进行查看,库文件中包含.so(共享对象文件,shared object),pli.a(静态库)等库文件,还需要*.tab(表格文件)来获取对应信息、索引等。

echo 用于获取环境变量,获取并打印,打印环境变量指令为:

echo $PATH | sed ‘s/ :/n/g’

which 查询当前路径是否设置成功,若设置成功会显示一个软件路径,查看verdi路径:which verdi

uname 查询当前系统信息,如硬件平台否为:x86_64

四、VCS+Verdi 如何dump波形

在dump波形时会用到那些命令,解决的是生成fsdb波形的问题,为了生成.fsbd格式的文件,可以使用verilog波形函数,也可以使用ucli/tcl接口:

(一)使用Verilog系统函数

作为小白,我觉得这种方式很友好,通过Verilog的PLI接口实现,在tb中添加两个函数:

initial begin
$fsdbDumfile(“uart.fsdb”);     //指定生成的fsdb文件的文件名
$fsdbDumpars(0,uart_byte_tx_tb); //指定dump的层次,0表示存储所有的wave,tb为起始层
end 

(二)、使用ucli/tcl接口

使用ucli/tcl接口时无需在tb中调用与fsdbDumpvars()函数,仅需在脚本中进行设置即可。在运行仿真时,打开ucli接口,通过Tcl脚本对fsdb进行设置,设置fsdb文件的文件名,设置fsdb文件的集成类型和起始文件:

global env

# tcl脚本引用环境变量,Makefile中通过export定义

fsdbDumpfile "$env(demo_name).fsdb"

# 设置波形文件名,受环境变量env(demo_name)控制

# demo_name在makefile中使用exportdemo_name=demo_fifo

fsdbDumpvars 0 "tb_top"

# 设置波形的顶层和层次,表示将tb_top作为顶层,Dump所有层次

run

+fsdb+autoflush

+fsdb+f+autoflush:用于开启一边仿真以一边Dump波形的功能,在不开启该功能时,运行完仿真之后,未退出命令行,直接在新终端中启动Verdi调用波性文件的话是一个用文件,没有波形,这是因为只有在结束仿真之后,波形才会Dump为静态文件供verdi调用,没有出现波形的原因是此时的.fsdb只是一个空文件,波形还未Dump,如下图所示:

此时可以在仿真的命令行中键入:fsdbDumpflush,启动波形Dump,在另一个终端中启动verdi加载波形,波形正常加载:

verdi优于modelsim也正是因此,通过tcl语言的控制,每次设置run时间,不断的加载仿真波形,十分方便!

VCS编译环节

无论采用调用系统函数还是调用tcl脚本,编译时两种操作相同,都是为了生成一个二进制可执行文件,其重点是VCS与Verdi的库进行连接:

-LDFLAGS
#表示下面要加载(load)的标志,将要传递VCS的Linker链接库,与接下来的两条指令配合使用。
-rdynamic
#加载动态库,提示需要加载动态库,如*库文件名录下的.so文件
-P $(Verdi_HOME)/share/PLI/VCS/LINUX64/novas.tab
#加载表格文件  
$(Verdi_HOME)/share/PLI/VCS/LINUX64/pli.a
#加载静态库

五、Makefile脚本设计

要用到的基本指令为:编译、仿真、verdi加载fsdb波形、清除文件 所以在脚本中设计伪指令:

.PHONY: com sim run_verdi clean

运行编译时会生成一个二进制可执行文件供仿真使用,该文件默认名为:simv,可通过脚本设置:

OUTPUT = uart #也可以自己设置名字 运行编译,设计编译指令,设置编译开关:

vcs表示运行编译,+v2k表示支持verilog2001标准,-timesacle=1ns/1ns用于设置仿真时间精度,-debug_all用于设置debug开关,-f dile_list.f用于设置编译文件,-o (OUTPUT)用于设置输出二进制可执行文件的文件名,-full64表示VCS为64位版本。

verdi加载fsdb文件显示波形:

-f file_list.f制定要加载的.v文件,-ssf $(OUTPUT).fsdb 表示打开verdi时默认自动加载.fsdb仿真文件,-nologo表示不显示欢迎界面。

六、两种fsdb文件生成方式的比较

基于系统函数

优点:熟悉verilog代码,接受较快

缺点

1.需要重新编译系统,浪费时间(不使用valuevalueplusargs时); 2.Verilog是低级语言,对于文本处理比较困难,不支持正则表达式

基于ucli/tcl接口:

优点:

1.不需要重新编译仿真顶层;

2.使用高级语言接口,容易完成复杂处理,例如传递变量,例如使用正则表达式;

3.交互式接口,控制灵活,仿真过程可修改dump信息,如dumpon/dumpoff

七、Verdi快捷键

  • save和restore 存储当前查看波形的工程 将当前所有信号存成一个*.rc文件 重新打开波形界面时,restore信号 nwave:file -> Save Signal/Restore Signal
  • 快速熟悉一个设计:通过nTRACE界面查看设计结构:

熟悉设计的IO Driver【D】(Input) 哪些信号驱动了当前信号,可以在nWAVE界面中进行查看。Load【L】 当前信号驱动了哪些信号,可以在nWAVE界面中进行查看。

操作演示:

最后分享一下我所用的工程,是一个UART发送的测试,其工程代码为:

module uart_byte_tx(
  Clk,
  Rst_n,
  data_byte,
  send_en,
  baud_set,
  
  Rs232_Tx,
  Tx_Done,
  uart_state
);

  input Clk;
  input Rst_n;
  input [7:0]data_byte;
  input send_en;
  input [2:0]baud_set;
  
  output reg Rs232_Tx;
  output reg Tx_Done;
  output reg uart_state;
  
  reg bps_clk;  //波特率时钟
  
  reg [15:0]div_cnt;//分频计数器
  
  reg [15:0]bps_DR;//分频计数最大值
  
  reg [3:0]bps_cnt;//波特率时钟计数器
  
  reg [7:0]r_data_byte;
  
  localparam START_BIT = 1'b0;
  localparam STOP_BIT = 1'b1;
  
  always@(posedge Clk or negedge Rst_n)
  if(!Rst_n)
    uart_state <= 1'b0;
  else if(send_en)
    uart_state <= 1'b1;
  else if(bps_cnt == 4'd11)
    uart_state <= 1'b0;
  else
    uart_state <= uart_state;
  
  always@(posedge Clk or negedge Rst_n)
  if(!Rst_n)
    r_data_byte <= 8'd0;
  else if(send_en)
    r_data_byte <= data_byte;
  else
    r_data_byte <= r_data_byte;
  
  always@(posedge Clk or negedge Rst_n)
  if(!Rst_n)
    bps_DR <= 16'd5207;
  else begin
    case(baud_set)
      0:bps_DR <= 16'd5207;
      1:bps_DR <= 16'd2603;
      2:bps_DR <= 16'd1301;
      3:bps_DR <= 16'd867;
      4:bps_DR <= 16'd433;
      default:bps_DR <= 16'd5207;      
    endcase
  end  
  
  //counter
  always@(posedge Clk or negedge Rst_n)
  if(!Rst_n)
    div_cnt <= 16'd0;
  else if(uart_state)begin
    if(div_cnt == bps_DR)
      div_cnt <= 16'd0;
    else
      div_cnt <= div_cnt + 1'b1;
  end
  else
    div_cnt <= 16'd0;
  
  // bps_clk gen
  always@(posedge Clk or negedge Rst_n)
  if(!Rst_n)
    bps_clk <= 1'b0;
  else if(div_cnt == 16'd1)
    bps_clk <= 1'b1;
  else
    bps_clk <= 1'b0;
  
  //bps counter
  always@(posedge Clk or negedge Rst_n)
  if(!Rst_n)  
    bps_cnt <= 4'd0;
  else if(bps_cnt == 4'd11)
    bps_cnt <= 4'd0;
  else if(bps_clk)
    bps_cnt <= bps_cnt + 1'b1;
  else
    bps_cnt <= bps_cnt;
    
  always@(posedge Clk or negedge Rst_n)
  if(!Rst_n)
    Tx_Done <= 1'b0;
  else if(bps_cnt == 4'd11)
    Tx_Done <= 1'b1;
  else
    Tx_Done <= 1'b0;
    
  always@(posedge Clk or negedge Rst_n)
  if(!Rst_n)
    Rs232_Tx <= 1'b1;
  else begin
    case(bps_cnt)
      0:Rs232_Tx <= 1'b1;
      1:Rs232_Tx <= START_BIT;
      2:Rs232_Tx <= r_data_byte[0];
      3:Rs232_Tx <= r_data_byte[1];
      4:Rs232_Tx <= r_data_byte[2];
      5:Rs232_Tx <= r_data_byte[3];
      6:Rs232_Tx <= r_data_byte[4];
      7:Rs232_Tx <= r_data_byte[5];
      8:Rs232_Tx <= r_data_byte[6];
      9:Rs232_Tx <= r_data_byte[7];
      10:Rs232_Tx <= STOP_BIT;
      default:Rs232_Tx <= 1'b1;
    endcase
  end  

endmodule

testbench为:


`define clk_period 20

module uart_byte_tx_tb;

  reg Clk;
  reg Rst_n;
  reg [7:0]data_byte;
  reg send_en;
  reg [2:0]baud_set;
  
  wire Rs232_Tx;
  wire Tx_Done;
  wire uart_state;
  
  uart_byte_tx uart_byte_tx(
    .Clk(Clk),
    .Rst_n(Rst_n),
    .data_byte(data_byte),
    .send_en(send_en),
    .baud_set(baud_set),
    
    .Rs232_Tx(Rs232_Tx),
    .Tx_Done(Tx_Done),
    .uart_state(uart_state)
  );
  
  initial Clk = 1;
  always#(`clk_period/2)Clk = ~Clk;
  
  initial begin
    Rst_n = 1'b0;
    data_byte = 8'd0;
    send_en = 1'd0;
    baud_set = 3'd4;
    #(`clk_period*20 + 1 )
    Rst_n = 1'b1;
    #(`clk_period*50);
    data_byte = 8'haa;
    send_en = 1'd1;
    #`clk_period;
    send_en = 1'd0;
    
    @(posedge Tx_Done)
    
    #(`clk_period*5000);
    data_byte = 8'h55;
    send_en = 1'd1;
    #`clk_period;
    send_en = 1'd0;
    @(posedge Tx_Done)
    #(`clk_period*5000);
    $finish;  
  end

 // initial begin: fsdb_generate
 // $fsdbDumpfile("uart.fsdb");
 // $fsdbDumpvars(0,uart_byte_tx_tb);
 // end 
endmodule

Makefile脚本为:

.PHONY: com sim run_verdi clean

OUTPUT =uart

export demo_name=$(OUTPUT)

#ALL_DEFINE = + define + BAUD_9600

#compile command 
VCS =vcs     +v2k  -timescale=1ns/1ns                    
     -debug_all                                          
     -LDFLAGS                                            
     -rdynamic                                          
     -P $(Verdi_HOME)/share/PLI/VCS/LINUX64/novas.tab    
      $(Verdi_HOME)/share/PLI/VCS/LINUX64/pli.a          
      -full64                                            
      +vcs_lic+wait                                      
      -f file_list.f                                    
      -o ${OUTPUT}                                      
      -l compile.log          

VERDI=verdi -f file_list.f   
      -ssf $(OUTPUT).fsdb    
      -nologo                
      -l v.log             

#sim command
SIM = ./$(OUTPUT)                              
      -ucli -i ../scripts/dump_fsdb_vcs.tcl    
      +fsdb+autoflush                          
      -l sim.log
    
#start compile
com:
  $(VCS)

#start simulation
sim:
  $(SIM)

#run verdi
run_verdi:
  $(VERDI) &
#clean
clean:
  rm -rf  ./verdiLog  ./dff ./csrc *.daidir *log *.vpd *.vdb simv* *.key *race.out* *.rc *.fsdb *.vpd *.log *.conf *.dat *.conf uart

tcl指令为:

global env
fsdbDumpfile "$env(demo_name).fsdb"
fsdbDumpvars 0 "uart_byte_tx_tb"
run 10000ns

好啦,本次更新到此结束,最后衷心的感谢相量子大哥公众号数字ICer的大哥,一位大哥提供了装好了虚拟机,另一位大哥在使用过程中提供了诸多使用技巧,感谢大家的关注。