system verilog中不可不小心的陷阱

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/anpingbo/article/details/77914761
  1. 任务和函数(task and function)
    任务可以调用函数以及其他任务,但是函数不可以调用任务。在verilog中函数必须有返回值,但是在system verilog中扩展了函数功能,其可以返回空,即void。同时task参数列表可以表示为类似C的形式,用括号括起来,如:
task test(input logic a, input reg[5:0]b, output logic c);

任务重可以有时序控制,但是函数不准许有时序控制。同时需要注意的坑是任务必须在所有语句执行完成后才会返回数值.
可以使用ref来传递参数,这有点类似C中指针传参,相当于将参数地址传递给任务,任务修改是可见的。但是在system verilog手册中要求ref只能用于动态任务中,即要声明任务为automatic。

task automatic test(input logic a, ref reg[5:0]b, output logic c);

不声明动态的任务或者函数,其默认为静态即内部数据分配给内存中固定存储区。如果任务被多次调用,那么多个程序共享静态存储区很容易发生混乱。声明为动态的任务和函数其内部变量自动为动态,但是可以通过static声明为静态的。
动态任务中的动态变量如果去读取文件,则不会获取文件数据,如:

task automatic test(input logic a, ref reg[5:0]b, output logic c);

    integer data;
    fp=$fopen(filename, "rb")
    $fread(data, fp);
endtask

data是没有数据的。如果将data声明为static,那么就可以获得数据了

task automatic test(input logic a, ref reg[5:0]b, output logic c);

    static integer data;
    fp=$fopen(filename, "rb")
    $fread(data, fp);
endtask

2.文件读写
在写激励的时候,很多时候要用到数据的缓存,由于采用RAM比较麻烦,还是利用文件来存储数据更为方便。用于读写数据的系统函数有很多,这里仅仅介绍我最常用的:$fread和$fwrite。
$fread函数主要用于读取二进制文件,它不会对文件的存储内容进行任何格式转化。调用形式为:
code=$fread(integral_var, fd);
code=$fread(mem, fd, start, count);
变量可以是任何压缩变量或者是存储变量,比如logic[7:0] a[256];start和count是可选的,表示开始地址和读取数量。fd是文件指针。code是返回值,如果读取失败会返回0,如果成功则返回读取字节个数。举一个读取文件的例子:

fd=$fopen(FILENAME, "rb");
if(fd==-1)begin
    $display("failed to open file: %s", FILENAME);
    return;
end
seek_code=$fseek(fd, address_offset, 0);//设置读取起始地址
read_code=$fread(data_array, fd);//读取数据到数组中
$fclose(fd);

system verilog中对文件读取比较简单,但是想要写入二进制,目前我没有找到直接的方式。使用$fwrite函数,会转化为ASCII形式存储到文件中。从手册中了解到可以设置写入方式:
$fwrite(fd, “%u”, data);
u表示是以二进制形式写入,但是vivado仿真器不支持。不知道其它仿真器。于是改用字符形式写入:
$fwrite(fd, “%c”, data);
在vivado下测试成功。但是听说别人使用其它仿真器时,对于0会被判定为NULL,不会写入。

3.vivado仿真
读写文件需要将文件加入工程,即点击添加文件->添加仿真文件->选择待读写文件->勾选include all design sources for simulation。
还有一个很大的坑,在initial或者always块中循环输出外部数据,总是会输出数据前一个值或者第一个数据输出两次。比如对于:

module initial_tb;

  reg clk;
  reg [7:0] data;
  reg valid;

  initial begin
    clk=1'b0;
    data=8'h0;
    valid=1'b0;
    repeat(5) @(posedge clk);
    valid=1'b1;
  end


  always begin

     begin
       if(valid)
          $display("data is: %d", data);
       @(posedge clk);
    end
  end

  always @(posedge clk)
  begin
    if(valid)
      data++;
  end

  always #20 clk=~clk;
endmodule

这个会打印出0,1,2…..
如果将valid信号延迟一个时钟,即增加

 always @(posedge clk)begin
    valid_r <=  valid;
  end

用valid_r来判定。则打印出正确数据,1,2,3…,即
这里写图片描述
但是valid_r和valid在同一个周期下。
因为valid有效和data++是同时执行的,之间没有延时,这样就会存在竞争。如果valid有效后,但是data++没有检测到;
如果将valid信号有效延时2ns,#2 valid=1’b1;打印数据就正确了。或者做#0时延,相当于在阻塞此进程,等待在同等时间条件下其它进程执行完毕。此时,在always快中,valid_r检测到valid为0。
其实在硬件电路中,valid是用于驱动data++的,也要保证满足setup 和holdup时间才行。
4.线程调度
system verilog提供了3种创建线程的方法:
(1)fork…join:块内语句同时执行;
(2)fork…join_any:块内语句有一个执行,则join_any后的语句(父进程)和块内进程(子进程)同时执行;
(3)fork…join_none:子进程和父进程同时执行。
对于以下语句:

initial begin
    for(int i=0;i<3;i++)
        fork
            $write(i);
        join_none
            #0 $display("\n");

end

打印结果为3,3,3…。因为#0时延阻塞了当前所有进程的调度。等到所有进程创建完成才执行。

猜你喜欢

转载自blog.csdn.net/anpingbo/article/details/77914761