3.1 过程语句
例3.1 新的过程语句和操作符
initial
begin : example
integer array[10],sum,j;
//在for语句中声明i
for(int i=0;i<10;i++)
array[i]=i;
//把数组里的元素相加
sum=array[9];
j=8;
do
sum+=array[j];
while(j--);
$display("Sum=%4d",sum);
end : example
例3.2 在读取文件时使用break和continue
initial begin
bit [127:0] cmd;
int file,c;
file=$fopen("commands.txt","r");
while(!$feof(file)) begin
c=$fscanf(file,"%s",cmd);
case(cmd)
"":continue;
"done":break;
...
endcase //case(cmd)
end
$fclose(file);
end
3.2 任务、函数以及void函数
在Verilog中:
1.任务和函数之间最重要的区别是:任务可以消耗时间,而函数不能。
2.函数里面不能待用#100之类的时延语句或者@(posedge clk)、wait(ready)的阻塞语句,也不能调用任务。
3.函数必须有返回值,且返回值必须被使用。
在SV中,允许函数调用任务,但只能在由fork...join_none语句生成的线程中调用。
**对于一个不消耗时间的SV任务,应该把它定义成void函数,这样它就能被任何任务或函数调用。
例3.3 用于调试的void函数
function void print_state( ... );
$display("@%0t:state=%s",$time,cur_state.name());
endfunction
在SV中,如果想调用函数并且忽略其返回值,可以使用void进行结果转换。有些仿真器如VCS可以不使用void而忽略返回值。
例3.4 忽略函数的返回值
void'($fscanf(file,"%d",i));
3.3 任务和函数概述
*一般情况下,不带参数的子程序在定义或者调用时不需要带空括号()。
*在SV子程序中,begin...end是可选的,task/endtask和function/endfunction已经足以定义子程序的边界。。
例3.5 不带begin...end的简单任务
task multiple_lines;
$display("First line");
$display("Second line");
endtask:multiple_lines
3.4 子程序参数
3.4.1 C语言风格的子程序参数
例3.6 Verilog-1995的子程序参数
task mytask2;
output [31:0] x;
reg [31:0] x;
input y;
...
endtask
例3.7 C语言风格的子程序参数
task mytask1(output logic [31:0] x,input logic y);
...
endtask:mytask1
3.4.2 参数的方向
缺省的类型和方向:logic input。
但不建议缺省。
3.4.3 高级的参数类型
ref:ref参数的传递方式为引用。ref参数只能被用于带自动存储的子程序中。
例3.10 使用ref和const传递数组
function void print_checksum(const ref bit [31:0] a[]);
bit [31:0] checksum=0;
for(int i-0;i<a.size();i++)
checksum^=a[i];
$display("The array checksum is %0d",checksum);
endfunction
*使用了const,则子程序无法修改传递的数组值。
*ref参数在任务中可以修改变量,而且修改结果对调用它的函数随时可见。
例3.11 在多线程间使用ref
task bus_read( input logic[31:0] addr,
ref logic[31:0] data);
//请求总线并驱动地址
bus.request=1'b1;
@( posedge bus.grant) bus.addr=addr;
//等待来自存储器的数据
@( posedge bus.enable) data=bus.data;
//释放总线并等待许可
bus.request=1'b0;
@(negedge bus.grant);
endtask
logic [31:0] addr,data;
initial
fork
bus_read(addr,data);
thread2:begin
@data;
$display("Read %h from bus",data);
end
join
3.4.4 参数的缺省值
在SV中,可以为参数指定一个缺省值,如果在调用时不指明参数,则使用缺省值。
3.4.5 采用名字进行参数传递
例3.14 采用名字进行参数传递
task many(input int a=1,b=2,c=3,d=4);
$display("%0d %0d %0d %0d",a,b,c,d);
endtask
initial begin
many(6,7,8,9);
many(); //1,2,3,4使用缺省值
many(.c(5)); //1,2,5,4
many(,6,.d(8)); //1,6,3,8
end
3.5 子程序的返回
return语句
从函数中返回一个数组
1. 定义一个数组类型。
2.使用ref参数。
3.将数组包装到一个类中,返回对象的句柄。
3.6 局部数据存储
3.6.1 自动存储
在SV中,模块(module)和program块中的子程序缺省情况下仍使用静态存储。
如果要使用自动存储,则必须在程序语句中加入automatic关键词。
例3.22 在program块中指定自动存储方式
program automatic test;
task wait_for_mem( input [31:0] addr,expect_data, output success);
while (bus.addr!==addr)
@(bus.addr);
success=(bus.data==expect_data);
endtask
...
endprogram
3.6.2 变量的初始化
例3.23 静态初始化的漏洞
program initialization; //有漏洞的版本
task check_bus;
repeat (5) @(posedge clk);
if(bus_cmd=='READ) begin
logic[7:0] local_addr=addr<<2;
$display("Local Addr=%h",local_addr);
end
endtask
endprogram
存在的漏洞:变量local_addr是静态分配的,在仿真一开始就有了初值。
解决办法:将程序块声明为automatic。或者将声明和初始化分离。
3.7 时间值
3.7.1 时间单位和精度
'timescale
timeunit
timeprecision
3.7.2 时间参数
module timing;
timeunit 1ns;
timeprecision 1ps;
initial begin
$timeformat(-9,3,"ns",8);
//-9代表ns,3表示3位小数,"ns"时间值后的字符串,8显示数值的最小宽度
#1 $display("%t",$realtime); //1.000ns
#2ns $display("%t",$realtime); //3.000ns
#0.1ns $display("%t",$realtime); //3.100ns
#41ps $display("%t",$realtime); //3.141ns
end
endmodule
3.7.2 时间和变量
time类型,64位整型
real类型,实型变量
时间量程和精度,时间会被缩放和舍入。
3.7.3 $time 和 $realtime
$time为根据所在模块时间精度要求进行舍入的整数。
$realtime为一个带小数部分的完整实数。