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时延阻塞了当前所有进程的调度。等到所有进程创建完成才执行。