zoukankan      html  css  js  c++  java
  • testbench常用语句 很详细相当实用

    内容

    与可综合Verilog代码所不同的是,testbench Verilog是在计算机主机上的仿真器中执行的。testbench Verilog的许多构造与C语言相似,我们可在代码中包括复杂的语言结构和顺序语句的算法。

    1 always块和initial

    Verilog有两种进程语句:always块和initial块。always块内的进程语句,可用来模拟抽象的电路。

    出于模拟的目的,always块可以包括:用以指定与不同结构之间的传播延迟等同的时序结构;或等待指定事件的时序结构。敏感列表有时可忽略。比方说,我们用下面的代码片段来模拟时钟信号,该信号每20个时间单位在0~1间变换一次,且永远执行下去。

    always

    begin

      clk=1;

      #20;

      clk=0;

      #20);

    end

    initial块内也有进程语句,但是仅在仿真之初被执行。其简单语法如下:

    initial

    begin

     进程语句;

    end

    initail块常用于设置变量的初始值。注意,initial块不可被综合。

    2 进程语句

    进程语句应用于initial块、always块、function和task之中。最常用的进程语句为:

    ·         阻塞赋值

    ·         非阻塞赋值

    ·         if表达式

    ·         case表达式

    ·         循环表达式

    我们讨论过阻塞和阻塞赋值if和case语句

    Verilog支持的循环结构有:for、while、repeat和forever。for循环的简单语法为:

    for([initial_assignment]; [end_condition]; [step_assignment])

    begin

      [procedural_statements;]

    end

    举个例子,我们可以使用下面的语句来清除16位寄存器文件的内容:

    integer i;

    . . .

    for(i=0; i<16; i=i+1)

      reg_file[i]=0;

    注意当循环体内只有一条语句的话,begin和end限定词可以略去。

    while循环的简单语法如下:

    while([end_condition])

    begin

      [procedural_statements;]

    end

    循环体内的语句连续重复执行,直到达到指定的终止条件[end_condition]为止。比方说上面的清寄存器文件的操作可以使用while循环来描述:

    integer i;

    . . .

    while(i<16)

    begin

      reg_file[i]=0;

      i=i+1;

    en

    repeat循环的简单语法如下:

    repeat([number])

    begin

      [procedual_statements;]

    end

    循环体内的语句被重复执行指定数次,该数可通过[number]来指定。比方说,我们可以将上面的操作替换为repeat循环:

    integer i;

    . . .

    repeat(16)

    begin

      [procedural_statements;]

    end

    forever循环,正如其名,重复执行其主体直至仿真结束位置。循环体内常包括一定的时序控制结构,以致周期性推迟执行。比方说,我们换一种方式来描述时钟信号,该信号每10个时间单位翻转一次,且永远运行下去。

    initial

    begin

      clk=1'b0;

      forever

        #10 clk=~clk;

    end

    3 时序控制

    在testbench中,必须指定不同信号有效和无效或等待某事件或条件的时间。有三种时序控制结构:

    ·         时延控制:#[delay_time]

    ·         事件控制:@([event], [event], …]

    ·         等待语句:wait([boolean_expression])

    此外还有一个编译器指令,`timescale,也与时序规范有关。

    4 时延控制

    时延控制使用#符号来指示,其后为延迟的时间单位数值。

    如果时延控制放置在左手边,那么整条语句的执行都会被延迟。比方说,

    . . .

    #10 a=1'b0;

    #5  y=a|b;

    . . .

    假设当前时间为t,上面的语句表示,a于t+10时刻得到0值;又过了5个时间单位后(即于t+15时刻)a|b表达式被计算,其结果被赋给y。

    如果实验控制被放置在右手边,那么表达式将会被立即运算,但是延迟后再赋给左手边。如:

    . . .

    #10 a=1'b0;

        y=#5 a|b;

    . . .

    a于t+10时刻得到0值;a|b表达式被立即运算(即在t+10时刻),但其结果却在t+15时刻才赋给y。

    一般情况下,我们使用时延控制生成激励的方式来替代传播延迟的模拟。下面的格式使得代码显得更加直观。

    . . .

    a=1'b0;// a gets 0

    #10;   // the 0 value lasts 10 time units

    a-1'b1;// a changes to 10

    #5;    // the 1 value lasts 5 time units

    a=1'b0;// a changes to 0

    #20    // the 0 value lasts 20 time units

    . . .

    5 事件控制

    事件控制使用@符号来指示,其后为敏感列表,用于指定所需事件。其使用与always块内的事件类似。事件即敏感列表中的信号改变其值(信号跳变)的时刻。可加入posedge和negedge关键字以指定所需的跳变边沿(上升沿和下降沿)。在testbench中,直到指定事件发生,语句才可跳过延迟,继续执行。事件控制的一个常见应用为:使用时钟信号来同步激励的生成。比方说,下面的代码片段中,en信号被激活持续一个时钟周期。

    localparam delta=1;

    . . .

    @(posedgeclk);// wait for the rising edge of clk

    #delta;        // wait for delta to avoid hold=time violation

    en=1'b1;       // assert en to 1

    @(posedgeclk);// wait for the next rising edge of clk

    #delta;        // wait for delta to avoid hold-time violation

    en=1'b0;       // assert en to 0     

    换一种方式,我们可以在时钟信号的下降沿断言或解除断言en。

    . . .

    @(negedgeclk)// wait for the falling edge of clk

    en=1'b1;      // assert en to 1

    @(negedgeclk)// wait for the next falling edge of clk

    en=1'b0;

    . . .

    6 等待语句

    wait语句用以等待指定条件。其简单语法如下:

    wait[boolean_expression]

    直到[boolean_expression]被计算为真,后面语句才可跳过延迟,继续执行。比方说,我们可以这样写代码:

    wait(state==READ && mem_ready==1'b1) [statement_to_get_data];

    我们也可以使用wait语句来延迟执行。比方说,我们可以等计数器数到15才激活某信号:

    . . .

    wait(counter==4'b1111);// wait until counter is 15

    . . .                  // continue

    wait语句有时很想事件控制。后者是等待某信号的跳变边沿,而前者是等待指定条件,有时可理解为电平敏感。

    7 timescale指令

    编译器指令用以控制编译和预处理verilog代码,他们通过重音符号(`)来指明。重音符号常位于键盘的左上角。与时间有关的指令是`timescale指令

    `timescale [time_unit] / [time_precision]

    time_unit指定计时和延时的测量单位,time_precision则是指定仿真器的精度。

    比方说,指令

    `timescale10ns/1ns

    则说明仿真单位为10ns,精度为1ns。当指定如下代码中的延时,

    #5 y = a & b; 

    表明实际上的延时为50ns(即5*10ns)。

    也可以指定小数形式的单位延时,比方说

    #5.12345 y = a & b;

    则说明实际延时为51.2345ns。因为精度是1ns, 所以在仿真中就取整为51ns。精度越少,仿真的准确性越高,但是会减慢仿真的速度。

    time_unit和time_precision的数字部分可以为1、10和100,时间单元可以是s(秒)、ms(毫秒)、us(微秒)、ns(纳秒)和ps(皮秒)。

    8 系统控制函数和任务

    Verilog有一组预定义的系统函数,以$打头,执行与系统相关的操作,如仿真控制、文件读取等。下面我们讲一下一些常用的函数和任务。

    数据类型转换函数

    $unsigned和$signed函数执行介于无符号数和有符号数类型之间的转换。

    仿真时间函数

    仿真时间函数返回当前的仿真时间,如$time、$stime和$realtime函数分别以64位整数、32位整数和实数的形式返回时间。

    仿真控制任务

    有两种仿真控制函数:$finish和$stop。其中,$finish任务用于终止仿真并跳出仿真器;$stop任务则用于中止仿真。在Modelsim中,$stop任务则是返回到交互模式。在开发流程中,我们有时会停在Modelsim环境中,来进一步编辑或测试波形,因此代码中使用的是$stop。

    显示任务

    在Modelsim中,仿真的结果可以以波形的形式显示,也可以以文本的形式显示。四种主要的显示任务有$display、$write、$strobe和$monitor,它们语法类似。在Modelsim中,文本是在控制面板显示的。

    $display的语法与C语言中的打印函数类似。其简单语法为:

    $display([format_string], [argument], [argument], ...);

    如:

    $display("at %d; signal x = %b", $time, x);

    其结果的形式如下:

    at 5100; signal x = 00110001

    最常用的转移符号有%d、%b、%o、%h、%c、%s和%g,对应分别为十进制、二进制、八进制、十六进制、字符、字符串和实数。

    $write任务几乎和$diplay等同,除了其执行之后并不跳到下一行显示。而是一直显示在当前位置。显示下一行字符 ,必须手动添加,以创建一个行中断。

    Verilog可结合time step的概念来塑造仿真延时。每个time step中可以发生很多活动。$strobe与$display任务类似。代替立即执行的是,$strobe任务是在当前仿真的time step的结尾执行的。它可以规避由于竞争冒险造成的不匹配的数据显示。

    $monitor任务是非常通用的命令。鉴于$displat、$write、$strobe任务是在一旦它们被执行的情况下才显示文本,$monitor任务则是当其参数发生变化时即显示文本。$monitor任务提供了简单的富有弹性的方式来跟踪仿真。比方说,我们可以在testbench中添加如下的代码:

    initial

    begin

      $display("time   test_in0   test_in1   test_out");

      $monitot("%d       %b          %b         %b",

                $time, test_in0, test_in1, test_out);

    end

    Modelsim的控制面板中显示的文本仿真结果如下(示例):

    time   test_in0   test_in1   test_out

       0      00         00          1

     200      01         00          0

     400      01         11          0

    . . .

    文件I/O系统函数和任务

    Veirlog提供一组用于访问外部数据文件的函数和任务。文件可以通过$fopen和$fclose函数来打开和关闭。$fopen的语法为:

    [mcd_names] = $fopen("[file_name]"); 

    $fopen函数返回一个与文件相关的32位的多通道描述子。这个描述子我们可以认为是一个32位的标志,它代表一个文件(亦即一个通道)。最低位LSB保留,用以只是标准输出(console)。当使用函数调用的文件被成功打开,则返回的描述子的值得某位会被置一。例如,0…0010表示打开第一个文件,0…0100表示打开第二个文件,依次类推。若函数的返回值为0,则表示文件未能成功打开。

    一旦某个文件被打开,我们就可以向其内写入数据。可用的四种显示系统任务为:$fdisplay,$fwrite,$fstrobe和$fmonitor。这些任务的用法类似于先前的$display等,除了其第一参数为描述子以外。

    $fdisplay([mcd_name], [format_string], ...); 

    下面给出一个简单的代码片段。

    integer log_file, both_file;

    localparam con_file =32'h0000_0001;// console

    initialbegin

        log_file  = $fopenZ("my_log");

        if(log_file == 0)

            $display("Fail to open log file");// write console

        both_file = log_file | con_file;

        

        // write to both console and log_file

        $fdisplay(both_file,"Simulation started");

        . . .

        // write to log_file only

        $fdisplay(log_file, ...);

        . . .

        // write to

        $fdisplay(both_file,"Simulation ended");

        $fclose(log_file);

    end

    注意我们可以通过对多个描述子进行位运算来创建一个描述子,比方说both_file变量。当both_file被使用时,就可以同时对console和log_file进行操作。

    有两个任务可以从文件中载入数据,分别为:$readmemb,$readmemh。这些任务假设外置文件中存储了memory-array的内容,然后读出这些内容存到一个变量中。$readmemb,$readmemh所假设的文件格式分别为二进制和十六进制,相应地,它们的语法格式为:

    $readmemb("[file_name]", [mem_variable]);

    $readmemh("{file_name]", [mem_variable]);

    下面的片段描述如何载入一个8x4的存储阵列:

    reg[3:0] v_mem [0:7];

    . . .

    $readmemb("vector.txt", v_mem);

    vector.txt应该包含八个4bit的使用空格分割的二进制数据。

    有了文件操作函数和任务,就可以使用外部文件来指定测试模型,以及记录仿真结果。下面给出一个案例。

    `timescale1ns/1ns

    moduleeq2_file_tb;

    // signal declaration

    reg[1:0] test_in0, test_in1;

    wiretest_out;

    integer log_file, console_file, out_file;

    reg[3:0] v_mem [0:7];

    integer i;

    // instantiate the circuit under test

    eq2_sop eq2_sop_inst (

        .a(test_in0),

        .b(test_in1),

        .aeqb(test_out)

    );

    initialbegin

        // setup output fil

        log_file = $fopen("eqlog.txt"

        if(!log_file) $fdisplay("Cannot open log file");

        console_file =32'h0000_0001;

        out_file = log_file | console_file;

        

        // read test vector

        #readmemb("vector.txt", v_mem);   

        // test generator iterating throught 8 pattens

        for(i=0; i<8; i=i+1)begin

            {test_in0, test_in1} = v_mem[i];

        end

        

        // stop simulation

        $fclose(log_file);

        $stop;

    end

    // text display

    initialbegin

        $fdisplay(out_file,"   time    test_in0    test_in1    test_out");

        $fdisplay(out_file,"           (a)         (b)         (aeqb)");

        $fmoitor(out_file,"    d    %b          %b          %b",

            $time, test_in0, test_in1, test_out);

    end

    endmodule

    指定的test pattern为4位二进制格式,存储在vector.txt中。文件内容为:

    1

    2

    3

    4

    5

    6

    7

    8

    00_00

    01_00

    01_11

    10_10

    10_00

    11_11

    11_01

    00_10

    注意:“_”只起连接数字,分隔开容易区分位数的作用,和verilog其他地方的用法一致。

    上面的文件被载入到二维的v_mem变量中。仿真的结果被写入console和log_file中。log_file的内容为:

    time    test_in0    test_in1    test_out

            (a)         (b)         (aeqb)

       0    00          00          1

     200    01          00          0

     400    01          11          0

     600    10          10          1

     800    10          11          0

    1000    11          00          1

    1200    11          01          0

    1400    00          10          0

    log_file为一般的文本文件,可使用其他文本编辑器编辑。

    9 用户自定义函数和任务

    待续

    转载自:http://blog.sina.com.cn/s/blog_78699cbf01016mvt.html

  • 相关阅读:
    背水一战 Windows 10 (61)
    背水一战 Windows 10 (60)
    背水一战 Windows 10 (59)
    背水一战 Windows 10 (58)
    背水一战 Windows 10 (57)
    背水一战 Windows 10 (56)
    背水一战 Windows 10 (55)
    背水一战 Windows 10 (54)
    背水一战 Windows 10 (53)
    背水一战 Windows 10 (52)
  • 原文地址:https://www.cnblogs.com/chengqi521/p/6732999.html
Copyright © 2011-2022 走看看