Verilog十大基本功2(testbench的设计 文件读取和写入操作 源代码)

需求说明:Verilog设计基础

内容       :testbench的设计 读取文件 写入文件

来自       :时间的诗


十大基本功之 testbench

1. 激励的产生


对于 testbench 而言,端口应当和被测试的 module 一一对应。
端口分为 input,output 和 inout 类型产生激励信号的时候,
input  对应的端口应当申明为 reg,
output 对应的端口申明为 wire,
inout  端口比较特殊,下面专门讲解。

1)直接赋值


一般用 initial 块给信号赋初值,initial 块执行一次,always 或者 forever 表示由事件激发反复执行。
举例,一个 module

`timescale 1ns/1ps

module exam();
  reg   rst_n;
  reg   clk;
  reg   data;
  
  initial
  begin
    clk = 1'b0;
    rst = 1'b1;
    #10
    rst = 1'b0;
    #500
    rst = 1'b1;
  end
  
  always
  begin
    #10 clk = ~clk;
  end
  
endmodule


大家应该注意到有个#符号,该符号的意思是指延迟相应的时间单位。该时间单位由 timscale 决定.


一般在 testbench 的开头定义时间单位和仿真精度,比如`timescale 1ns/1ps
前面一个是代表时间单位,后面一个代表仿真时间精度。
以上面的例子而言,一个时钟周期是 20 个单位,也就是 20ns。


而仿真时间精度的概念就是,你能看到 1.001ns 时对应的信号值,
而假如 timescale 1ns/1ns,1.001ns 时候的值就无法看到。
对于一个设计而言,时间刻度应该统一,如果设计文件和 testbench 里面的时间刻度不一致,
仿真器默认以 testbench 为准。


一个较好的办法是写一个 global.v 文件,然后用 include 的办法,可以防止这个问题。


对于反复执行的操作,可写成 task,然后调用,比如

  task load_count;
  
  input [3:0] load_value;
    begin
      @(negedge clk_50);
      $display($time, " << Loading the counter with %h >>", load_value);
      load_l   = 1’b0;
      count_in = load_value;
      @(negedge clk_50);
      load_l   = 1’b1;
    end
  endtask //of load_count
  
  initial
  begin
    load_count(4’hA); // 调用 task
  end


其他像 forever,for,function 等等语句用法类似,虽然不一定都能综合,但是用在 testbench 里面很方
便,大家可以自行查阅参考文档

2) 文件输入


有时候,需要大量的数据输入,直接赋值的话比较繁琐,可以先生成数据,再将数据读入到寄存器中,
需要时取出即可。
用 $readmemb 系统任务从文本文件中读取二进制向量(可以包含输入激励和输出期望值)。
$readmemh 用于读取十六进制文件。例如:


reg   [7:0]  mem[256:1] // a 8-bit, 256-word 定义存储器 mem
initial $readmemh ( "E:/readhex/mem.dat", mem ) // 将.dat 文件读入寄存器 mem 中
initial $readmemh ( "E:/readhex/mem.dat", mem, 128, 1 ) // 参数为寄存器加载数据的地址始终

文件调入和打印中,我们 $fread $fwrite 用的更多一些, 大家可以自行查阅参考文档


2. 查看仿真结果


对于简单的 module 来说,要在 modelsim 的仿真窗口里面看波形,就用 add wave ..命令
比如, testbench 的顶层 module 名叫 tb,要看时钟信号,就用 add wave tb.clk
要查看所有信号的时候,就用 add wave /*  

当然,也可以在 workspace 下的 sim 窗口里面右键单击 instance 来添加波形
对于复杂的仿真,免不了要记录波形和数据到文件里面去。

1)波形文件记录(这部分暂时我没用到呢!)

常见的波形文件一般有两种, vcd 和 fsdb, debussy 是个很好的工具,支持 fsdb,所以最好是 modelsim+debussy 的组
合默认情况下, modelsim 不认识 fsdb,所以需要先装 debussy,再生成 fsdb 文件。


$dumpfile 和$dumpvar 是 verilog 语言中的两个系统任务, 可以调用这两个系统任务来创建和将指定信息导入 VCD 文件.
对于 fsdb 文件来说,对应的命令是 fsdbDumpfile,dumpfsdbvars


(什么是 VCD 文件? 答: VCD 文件是在对设计进行的仿真过程中,记录各种信号取值变化情况的信息记录文件。 EDA
工具通过读取 VCD 格式的文件,显示图形化的仿真波形,所以,可以把 VCD 文件简单地视为波形记录文件.)下面分别
描述它们的用法并举例说明之。

$dumpfile 系统任务:为所要创建的 VCD 文件指定文件名。
举例( "//"符号后的内容为注释文字):
initial
$dumpfile ("myfile.dump"); //指定 VCD 文件的名字为 myfile.dump,仿真信息将记录到此文件
$dumpvar 系统任务:指定需要记录到 VCD 文件中的信号,可以指定某一模块层次上的所有信号,也可以单独指定某一
个信号。
典型语法为$dumpvar(level, module_name); 参数 level 为一个整数, 用于指定层次数, 参数 module 则指定要记录的模块。
整句的意思就是,对于指定的模块,包括其下各个层次(层次数由 level 指定)的信号,都需要记录到 VCD 文件中去。


举例:
  initial
    $dumpvar (0, top); //指定层次数为 0,则 top 模块及其下面各层次的所有信号将被记录

  initial
    $dumpvar (1, top); //记录模块实例 top 以下一层的信号
  //层次数为 1,即记录 top 模块这一层次的信号
  //对于 top 模块中调用的更深层次的模块实例,则不记录其信号变化

  initial
    $dumpvar (2, top); //记录模块实例 top 以下两层的信号

  //即 top 模块及其下一层的信号将被记录
  假设模块 top 中包含有子模块 module1,而我们希望记录 top.module1 模块以下两层的信号,则语法举例如下:
  initial
    $dumpvar (2, top.module1); //模块实例 top.module1 及其下一层的信号将被记录
  假设模块 top 包含信号 signal1 和 signal2(注意是变量而不是子模块), 如我们希望只记录这两个信号,则语法举例如下:

  initial
    $dumpvar (0, top.signal1, top.signal2); //虽然指定了层次数,但层次数是不影响单独指定的信号的


  //即指定层次数和单独指定的信号无关
  我们甚至可以在同一个$dumpvar 的调用中,同时指定某些层次上的所有信号和某个单独的信号,假设模块 top 包含信
  号 signal1,同时包含有子模 块 module1,如果我们不但希望记录 signal1 这个独立的信号,而且还希望记录子模块 module1
  
  以下三层的所有信号,则语法举例如下:
  

initial
    $dumpvar (3, top.signal1, top.module1); //指定层次数和单独指定的信号无关
                                            //所以层次数 3 只作用于模块 top.module1, 而与信号
  top.signal1 无关


  
  上面这个例子和下面的语句是等效的:
  
initial
    begin
    $dumpvar (0, top.signal1);
    $dumpvar (3, top.module1);
  end
  
$dumpvar 的特别用法(不带任何参数):
  initial
  $dumpvar; //无参数,表示设计中的所有信号都将被记录


最后,我们将$dumpfile 和$dumpvar 这两个系统任务的使用方法在下面的例子中综合说明,假设我们有一个设计实例,
名为 i_design,此设计中包含模块 module1,模块 module1 下面还有很多层次,我们希望对这个设计进行仿真,并将仿
真过程中模块 module1 及其以下所有层次中所有信号的变化情况,记录存储到名为 mydesign.dump 的 VCD 文件中去,
则例示如下:

  initial
  begin
    $dumpfile ("mydesign.dump"); //指定 VCD 文件名为 mydesign.dump
    $dumpvar (0, i_design.module1); //记录 i_design.module1 模块及其下面层次中所有模块的所有信号
  end
  
  对于生成 fsdb 文件而言,也是类似的
  initial
  begin
    $fsdbDumpfile("tb_xxx.fsdb");
    $fsdbDumpvars(0,tb_xxx);
  end

2)文件输出结果

  integer out_file; // out_file 是一个文件描述,需要定义为 integer 类型
  out_file = $fopen ( " cpu.data " ); // cpu.data 是需要打开的文件,也就是最终的输出文本
设计中的信号值可以通过$fmonitor, $fdisplay,$fwrite
其中$fmonitor 只要有变化就一直记录, $fdisplay 和$fwrite 需要触发条件才记录
例子:
  
initial begin
    $fmonitor(file_id, "%m: %t in1=%d o1=%h", $time, in1, o1);
    end
    always@(a or b)
    begin
    $fwrite(file_id,"At time%t a=%b b=%b",$realtime,a,b);
  end
  



3 参考“A Verilog HDL Test Bench Primier.pdf”

1) DUT(Design Under Test)部分

//-------------------------------------------------
// File: count16.v
// Purpose: Verilog Simulation Example
//-------------------------------------------------
`timescale 1 ns / 100 ps
module count16 (
       clk,
       rst_n,
       load_l,
       enable_l,
       cnt_in,
       oe_l,
       
       count,
       count_tri
       );
      
  input         clk;
  input         rst_n;
  
  input         load_l;
  input         enable_l;
  input  [3:0]  cnt_in;
  input         oe_l;
                
  output [3:0]  count;
  output [3:0]  count_tri;
  
  reg    [3:0]  count;
  
  // tri-state buffers
  assign count_tri = (!oe_l) ? count : 4'bZZZZ;
  
  
  // synchronous 4 bit counter
  
  always @ (posedge clk or negedge rst_n)
  if (!rst_n) begin
    count <=  4'd0;
  end
  else begin
  	if (!load_l) begin
      count <=  cnt_in;
    end
    else if (!enable_l) begin
      count <=  count + 1;
    end
  end
  
endmodule //of count16

2) Test Bench 

//-------------------------------------------------
// File: tb.v
// Purpose: Verilog Simulation Example
// Test Bench
//-----------------------------------------------------------
`timescale 1ns / 100ps
module tb ();
  
  //---------------------------------------------------------
  // inputs to the DUT are reg type
  reg         clk_50;
  reg         rst_n;
  reg         load_l; 
  reg         enable_l;
  
  reg  [3:0]  count_in;
  reg         oe_l;
  
  //--------------------------------------------------------
  // outputs from the DUT are wire type
  wire [3:0]  cnt_out;
  wire [3:0]  count_tri;
  
  
  //----------------------------------------------------------
  // create a 50Mhz clock
  always  #10 clk_50 = ~clk_50; // every ten nanoseconds invert
  
  
  //-----------------------------------------------------------
  // initial blocks are sequential and start at time 0
  initial
  begin
    $display($time, " << Starting the Simulation >>");
    clk_50 = 1'd0;
    
    // at time 0
    rst_n = 0;
    
    // reset is active
    enable_l = 1'd1;
    
    // disabled
    load_l = 1'd1;
    
    // disabled
    count_in = 4'h0;
    oe_l = 4'b0;
    
    // enabled
    #20 rst_n = 1'd1;
    
    // at time 20 release reset
    $display($time, " << Coming out of reset >>");
    
    @(negedge clk_50); // wait till the negedge of
                       // clk_50 then continue
    load_count(4'hA);
    
    // call the load_count task
    
    @(negedge clk_50);
    $display($time, " << Turning ON the count enable >>");
    
    enable_l = 1'b0;
    // turn ON enable
    // let the simulation run,
    // the counter should roll
    wait (cnt_out == 4'b0001); // wait until the count
    
    // equals 1 then continue
    $display($time, " << count = %d - Turning OFF the count enable >>",cnt_out);
    enable_l = 1'b1;
    #40;
    
    // let the simulation run for 40ns
    // the counter shouldn't count
    $display($time, " << Turning OFF the OE >>");
    oe_l = 1'b1;
    
    
    // disable OE, the outputs of
    // count_tri should go high Z.
    #20;
    $display($time, " << Simulation Complete >>");
    $stop;
    
    // stop the simulation
  end
  
  
  //--------------------------------------------------------------
  // This initial block runs concurrently with the other
  // blocks in the design and starts at time 0
  
  initial begin
  // $monitor will print whenever a signal changes
  // in the design
    $monitor(
      $time, 
      " clk_50=%b, rst_n=%b, enable_l=%b, load_l=%b, count_in=%h, cnt_out=%h, oe_l=%b, count_tri=%h",
      clk_50, rst_n, enable_l, load_l, count_in, cnt_out, oe_l, count_tri
    );
  end
  
  
  //--------------------------------------------------------------
  // The load_count task loads the counter with the value passed
  task load_count;
    input [3:0] load_value;
    
    begin
      @(negedge clk_50);
        $display($time, " << Loading the counter with %h >>", load_value);
        load_l = 1'b0;
        count_in = load_value;
    
      @(negedge clk_50);
        load_l = 1'b1;
    end
  endtask //of load_count
  
  
  
  //---------------------------------------------------------
  // instantiate the Device Under Test (DUT)
  // using named instantiation
  count16 count16_m0 (
    .clk       (clk_50),
    .rst_n     (rst_n),
    .load_l    (load_l),
    .cnt_in    (count_in),
    .enable_l  (enable_l),
    .oe_l      (oe_l),
    
    .count     (cnt_out),
    .count_tri (count_tri)
  );
  
  
  
  //---------------------------------------------------------
  // read and write data
  reg  [7:0] mem[10:1];//read data from file
  
  initial $readmemh ("F:/IC/prj/testbench/prj0/data/mem.dat", mem ); // 将.dat 文件读入寄存器 mem 中
  
  
  //设计中的信号值可以通过$fmonitor, $fdisplay,$fwrite
  //其中$fmonitor 只要有变化就一直记录, $fdisplay 和$fwrite 需要触发条件才记录
  
  integer file_out; // out_file 是一个文件描述,需要定义为 integer 类型
  initial file_out = $fopen("F:/IC/prj/testbench/prj0/data/wr_mem.dat", "w");
  
  // wr_mem.dat 是需要打开的文件,也就是最终的输出文本
  
  always @(posedge clk_50)
    if (/*tb.count16_m0.*/enable_l == 1'd0) begin
      $fwrite (file_out, "%h\n", cnt_out[3:0]);
//      $fdisplay(file_out, "%h", cnt_out[3:0]);
    end
  
  
endmodule //of cnt16_tb

3) sim.do文件

  #Time: 2016-07-26
  #By  : times_poem
  
  quit -sim
  
  cd F:/IC/prj/testbench/prj0
  
  if [file exists work] {
     vdel -all
  }
  
  vlib work 
  
  vlog ./*.v
  vlog ./src/*.v
   
  vsim -t ps -novopt work.tb
  
  log -r /*
  do wave.do 
  
  run -all



  
  • 61
    点赞
  • 391
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: 在Verilog中,testbench被用于验证设计是否能够按照预期的方式工作。Verilog testbench可以用多种方式立足,其中之一就是通过读取tcl文件来执行模拟过程。 tcl是一种解释性脚本语言,可用于自动化和测试,因此可以很好地用于testbench设计中。通常,tcl脚本中包含了一系列的命令和操作,这些命令和操作可以被testbench所识别和执行。 Verilog testbench读取tcl可以通过vlog命令行来完成,例如: vlog -f simulate.tcl testbench.v design.v 其中simulate.tcl是一个tcl脚本文件,包含testbench的测试代码。testbench.v和design.v是要被模拟的Verilog文件。 在simulate.tcl中,我们可以编写一些tcl命令来模拟testbench过程,包括波形生成、信号赋值、时钟脉冲和仿真控制等。例如: # simulation commands waveform_config design_tb.vcd input1 = 1'b0 input2 = 1'b1 # clock pulse generation forever { # assert clock low clk = 1'b0 # wait for specified time delay 5 # assert clock high clk = 1'b1 # wait for specified time delay 5 } 这段代码中,我们首先使用“waveform_config”命令来配置信号的波形。然后,我们定义了两个输入信号“input1”和“input2”,并进行了赋值。接下来,我们使用“forever”命令来构造一个时钟脉冲,通过不断地切换clock信号的高低电平来实现。 总之,Verilog testbench读取tcl是一种非常方便的验证方式,通过tcl脚本中的命令和操作,我们能够对整个测试流程进行完整的控制和管理,从而提高测试效率和准确性。 ### 回答2: Verilog Testbench 是用于模拟设计的测试环境,它的作用就是通过一系列测试向设计环境中输入数字信号,然后观察输出信号是否正确。Testbench 的重要性在于验证设计的正确性,有助于识别和修复设计中的潜在问题。在 Testbench 中使用 TCL 的主要目的是通过脚本语言生成测试向量序列,从而有效地减少手动调整测试向量的时间和精力。 Verilog Testbench 可以通过 TCL 脚本读取参数文件,以指定测试向量以及其他需要的参数,这样就可以利用 TCL 脚本自动生成测试向量序列。通过这种方式,可以大大减少测试向量序列的编写和修改时间,并且可以确保正确性。TCL 脚本还可以用于控制仿真的执行流程,例如,它可以在仿真运行期间读取仿真输出并进行分析,提高仿真效率,同时还可以在特定情况下中断仿真运行,以节省时间和资源。 总之,使用 TCL 脚本作为 Verilog Testbench 的一部分可以提高设计验证的效率和准确性,因为它为测试向量的生成和仿真过程的控制提供了一种自动化和可扩展的方式。 ### 回答3: Verilog Testbench可以通过读取TCL脚本文件来完成自动化测试。TCL(Tool Command Language)是一种脚本语言,可以帮助测试工程师编写测试用例和测试流程,实现自动测试。 读取TCL文件的方法是在Verilog Testbench中调用TCL解释器来识别和执行TCL脚本文件中的命令。在Testbench中可以使用TCL的eval命令来执行TCL脚本中的命令,也可以使用source命令来执行整个TCL脚本文件。 对于Verilog Testbench中需要读取TCL文件的场景,通常需要定义TCL脚本文件的路径,并在Testbench中使用open命令打开TCL文件,之后使用read命令将TCL文件的内容读入到一个变量中,最后使用eval或者source命令执行TCL脚本中的命令。 通过读取TCL文件Verilog Testbench可以快速编写并执行大量的测试用例,以验证设计的正确性和稳定性。同时,测试工程师也可以根据需要修改TCL脚本文件,实现更加灵活的测试流程和测试策略。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值