always和initial
需要清楚哪些语句需要放到硬件世界,哪些语句需要放到软件世界!
- module/endmodule,interface/endinterface被视为硬件世界!
- program/endprogram,class/endclass被视为软件世界!
过程语句always
always为了描述硬件电路的行为,使用时需要注意:哪种方式是时序电路描述,哪种方式是组合电路描述;
always中的@(event)敏感列表是为了模拟硬件信号的触发行为,需要正确对标硬件行为和always过程块描述;
always过程块是用来描述硬件时序电路和组合电路的正确打开方式,因此只能使用在module或interface中。
块语句initial
initial只执行一次;
initial无法被延迟执行,仿真一开始都会同时执行,不同initial和always之间的顺序上没有顺序科研,因此不能将它们在代码中的前后顺序与执行顺序画上等号;
initial从执行路径的属性来看,不应该存在于硬件设计的代码中,它本身不可综合,对描述硬件电路没有任何帮助;
initial是为了测试而生由于测试需要按照时间顺序的习惯即软件的方式来完成,所以initial可以实现这一要求。
initial可以放在module、interface、program中,如果在program中加循环的话,必须使用initial里,并且用begin…end括住。
软件的过程语句function和task
function
可以在参数列表中指定输入参数(input)、输出参数(output)、输入输出参数(inout)或者引用参数(ref)。
可以指定返回值也可以不指定返回值(void).
function int double(input int a); //a类似于C语言中的指针
return 2*a;或者 double=2*a; //在systemverilog中函数名作为变量去接收参数传回去的值
endfunction
initial begin
$display("double is %0d is %0d",10,double 10);
end
SV函数的特点:
默认数据类型是logic,例如input [7:0] addr。
- 数组可以作为形式参数传递`
- function可以返回或者不返回结果,如果返回结果需要用关键字return,如果不返回则应该声明function时采用void function()。
- 只有数据变量可以在形式参数列表中被声明为ref类型,而线网类型不饿能被声明为ref类型。
- 在使用ref时,有时候为了保护数据对象不被写入,可以通过const的方式来限定ref声明的参数。
- ref相当于指针,在函数内执行操作时,在函数外可以看到变量的修改。
任务task
任务task相比函数要更加灵活,且有以下不同点:
- task无法通过return返回结果,但是可以返回控制,因此只能通过output、input或者ref 的参数来返回。
- task内可以置入耗时语句,而function不能,常见的耗时语句包括@event、wait event、#delay等。
- 在task中可以调用function,但是在function中不能调用task。
task mytask (output logic[31:0] x,input logic y);
...
endtask
function和task的使用
- 和时序无关的代码可以放在function中,和时序相关的语句使用task。这样做的好处是在遇到这两种方法的定义时,可以知道function只能运用于纯粹的数字或者逻辑运算,而task则被运用于需要耗时的信号采样或者驱动场景。
- function和task都可以调用function;但是建议task只调用task,因为如果被调用的task中内置耗时语句时,外部调用的方法类型必须为task。
wait和@
wait—高电平执行
wait()中的内容为电平触发,只要()中的内容为1就可以触发,@()中的内容是0/1跳变触发。
wait只等待一次,@是每时每刻都在等待;
@—clk只要变化就会执行。
- 并行的initial是从0时刻开始同时执行的。
子程序参数
Verilog和SystemVerilog的语句对比
task mytask;
output[31:0] x;
reg[31:0] x;
input...;
endtask
在SV中,简单明了
task mytask2(output logic[31:0] x,
input logic y);
...
endtask
参数传递
使用类似port的语法指定子程序参数名字的方式来传递参数。
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(3,4,5,6); //3,4,5,6 指定所有值
many(); //1,2,3,4 默认传参
many(.c(2)); //1,2,2,4 指定c
many(,6,.d(3)); //1,6,3,3 混合传参,不推荐
end
参数传递的方向
缺省的类型和方向是“logic 和input”。
复杂的verilog参数传递
task t3;
input a,b;
logic a,b;
output [15:0] u,v;
bit [15:0] u,v;
...
endtask
简单的SV的声明方式
task t3(a,b output bit[15:0] u, v);
...
endtask
其中,a和b都是位宽为1的bit的logic输入,参数u和v是16bit的输出;但是建议对子程序参数的声明都带上类型和方向
ref参数类型
在参数传递中,参数的传递方式可以指定为引用ref而并非复制,ref参数类型比input、output或inout更好用,可以使用ref将数组传递给子程序。
function automatic void print_csm(const ref bit[31:0] a[])
bit[31:0] checksum=0;
for(int i=0;i<a.size();i++)
checksum = checksum ^ a[i];
$display("The array checksum is %h",checksum);
endfunction
在上述代码中,如果不使用ref进行数组的传递,数组会被复制到堆栈区,而不是在内部改变,外部也改变。
但是使用了ref参数类型,并且指明函数是automatic类型,automatic类型表明在子程序内部是自动存储的。表明当在内部进行修改变量时,修改的结果可以对它所调用的函数也是随时改变的,相当于使用了一块内存空间。如果在子程序内不希望改变数组的值,可以使用const对ref进行修饰。类似于C++中const的使用
子程序返回
return语句—和c语言的使用方法类似,用来返回一个值。
从子程序中返回一个数组
定义一个数组类型,在函数的声明中使用该类型
typedef int array[5];
array f5;
function array init(input int start) //创建了一个array类型的函数init,使用函数名接收传递出来的数组
foreach(init[i])
init[i]=i+start;
endfunction
initial begin
f5=init(5); //f5是一个数组类型的变量,接收init,在这里init函数返回的数组被复制到f5中
foreach(f5[i])//遍历
end
第二种方式是把数组作为ref参数传递给函数
function automatic void init(ref int f[5],input int start);
foreach(f[i])
f[i]=i+start;
endfunction
int fa[5];
initial begin
init(fa,5);
foreach(fa[i])
$display
end
第三种方式的将数组包装在一个类中,返回对象的句柄。
局部数据存储
默认的module和program中的子程序是静态存储的。静态存储指子程序参数或者局部变量是被存放在固定的位置的,不像其他的语言那样放在堆栈区。
在SV中,如果在测试程序的多个地方调用同一个任务,由于任务的局部变量会使用共享的静态存储区,所以不同的线程之间会窜用局部变量。如果使用自动存储,就可以迫使仿真器使用堆栈区存储局部变量。
自动存储的关键字是automatic
时间值
如果使用`timescale时,必须注意的是编译时的文件顺序,确保所有的时延都采用适当的量程和精度。
有时需要使模块、接口或程序块与时间单位和精度信息直接绑定,有时需要指定class中的时间单位等
timeunit和timeprecision可以为每个模块指定其时间单位和精度。,也可以通过**$timeformat(),$time**,**$realtime **是代码在时间标度上更清楚. $timeformat的4个参数分别是时间标度、小数点后的数据精度、时间值后的后缀字符串以及显示数值的最小宽度。其中第一个参数的时间标度如下表所示:
timeunit和timeprecision可以使用在module、program、package、interface等结构中,但是不能使用在class中,而timescale可以使用在任何地方,这也导致了其独立性不够,容易因为文件编译顺序等原因导致期望的时间延迟不对的情况出现。
timeformat用于配置其他打印系统任务中($write, d i s p l a y , display, display,strobe, $monitor, $fwrite, $fdisplay, $fstrobe, and $fmonitor)%t类型的打印格式,
在下面的代码中,设定时间单位为1ns,时间精度为1ps,timeformat中四个参数设定。如果不指定,则使用默认的格式,即时间单位为`timescale设置的时间精度,不精确到小数点后,打印的字符串为空,最小长度为20个长度。
%t 通常用于打印当前的仿真时间,display中的系统函数realtime指获取当前实时时间。
**timeunit:用于指定时间单位,也可以同时指定时间精度;**
**timeprecision:用于指定时间精度;**
`timescale 1 ns / 1 ns
module dut_time(clk);
timeunit 1ns;
timeprecision 10ps;
`timescale 10 ns / 1 ns
output clk;
reg clk;
initial begin
clk = 1'b0;
forever #1.125 clk = ~clk;
end
endmodule
`timescale 10 ns / 1 ns
module top_tb;
reg clk;
wire clkt;
dut_time dut_t(clkt);
initial begin
$printtimescale(top_tb);
//打印时间格式timescale,输出形式:Time scale of (module_name) is unit/precision
$printtimescale(top_tb.dut_t);
clk = 1'b0;
forever #1.55 clk = ~clk;
end
endmodule
当设定timeunit和timeprecision时,时间单位和精度是设定的,和timescale无关。如果未设定,则采用默认的时间单位和精度或者timescale中设定的
在SV中,可以把时间值存储在变量中,并且在计算和延时中使用。根据当前时间量程和精度,时间值或被缩小或舍入。time类型的变量不能保存小数时延,时延的小数部分会被舍掉,可以采用realtime变量保存小数时间变量。
获取仿真时间的系统函数:$time,$stime,$realtime。
·timescale 1ns/1ps
program tb_top;
initial begin
#1.5ns;
$display("$time=%p",$time);//进位64bit的整数
#1.2ns;
$display("$time=%p",$stime);//32位的整数
#1.5ns;
$display("$time=%p",$realtime);
#0.2ns;
$display("$time=%p",$realtime);//无符号小数,不用进位
$printtimescale();
end
endprogram
实例2: