一、过程语句

  可以在 begin 或 fork 语句中使用标识符,然后在相对应的 end 和 join 语句中放置相同的标号,这使得程序块的首尾匹配更加容易。也可以把标识符放在其他语句里,如 endmodule、endtask、endfunction 等。

  SV为循环功能增加了两个新语句,第一个是 continue,用于再循环中跳出本轮循环剩下的语句而直接进入下一轮循环。第二个是 break,用于终止并跳出循环。

二、任务、函数以及void函数

1. void 函数

  在Verilog中,任务(task)和函数(function)之间有明显的区别,其中最重要的一点是任务可以消耗时间,而函数不能。函数里面不能带有诸如 #100的时延语句或 @(posedge clk),wait(ready) 的阻塞语句,也不能调用任务。此外,Verilog中的函数必须有返回值,并且返回值必须调用,例如用到赋值语句中。SV 中允许函数调用任务,但只能在由 fork...join_none 语句生成的线程中调用。

  如果你有一个不消耗时间的SV任务,你应该把它定义成 void 函数,这种函数没有返回值。它可以被其它任务或函数调用。

eg:function void print_state(...);
      $display(...);
    endfunction

  在SV中,如果想调用函数并且忽略它的返回值,可以使用 void 进行类型转换。eg:void`($fscanf(file, "%d", i));  // 忽略函数的返回值

2. 任务和函数概述

  一般情况下,不带参数的子程序在定义或调用时并不需要带空括号( )。

  (1)在子程序中去掉 begin...end

  在 Verilog 中,begin...end 对单行以外的子程序都是必须的。在SV中,begin...end 块变成可选的了,task/endtask 和 function/endfunction 的关键词已经足以定义这些子程序的边界。

三、子程序参数

1. 参数的方向

  声明子程序参数时,缺省的类型是 logic ,方向是 input。

  缺省情况下,参数的类型是与前一个参数相同的。eg:task sticky (ref int array[50], int a, b);  // a 和 b 的参数方向是与前一个参数一致的 ref 类型。

2. 高级的参数类型

  Verilog对参数的处理方式很简单:在子程序的开头把 input 和 inout 的值复制给本地变量,在子程序退出时则复制 output 和 inout 的值。除标量以外,没有任何把存储器传递给Verilog子程序的办法。

  在SV中,参数的传递方式可以指定为引用而不是复制。这种 ref 参数类型比 input、output 或 inout更好用。

system verilog 队列删除 system verilog fork_system verilog 队列删除

 

   SV允许不带 ref 进行数组参数的传递,这是数组会被复制到堆栈区里。这种操作的代价很高,除非是特别小的数组。

  SV的语言手册规定了 ref 参数只能被用于带自动存储的子程序中。如果对子程序或模块指明了 automatic 属性,则整个子程序内部都是自动存储的。上述例子中用到 const 修饰符。其结果是,虽然数组变量a指向了调用程序中的数组,但子程序不能修改数组的值。如果你试图修改数组的值,仿真器会报错。

  使用 ref 的第二个好处是在任务里可以修改变量而且修改结果对调用它的行数随时可见。当有若干个并发线程时,可以提供一种简单的信息传递方式。

四、局部数据存储

1. 自动存储

  Verilog-1995中,如果试图在测试程序里的多个地方调用同一个任务,由于任务里的局部变量会使用共享的静态存储区(放在固定位置),所以不同的线程之间会窜用这些变量。在Verilog-2001中,可以指定任务、函数和模块使用自动存储,从而迫使仿真器使用使用堆栈区存储局部变量。在SV中,模块(module)和 program 块中的子程序在缺省情况下仍然使用静态存储。如果要使用动态存储,则必须在程序语句中加入 automatic 关键词。

system verilog 队列删除 system verilog fork_system verilog 队列删除_02

 

  因为参数 addr 和 expect_data 在每次调用时都使用不同的存储空间,所以对这个任务同时进行多次调用是没有问题的。但如果没有修饰符 automatic,由于第一次调用的任务处于等待状态,所以对wait_for_mem的第二次调用会覆盖它的两个参数。

2. 变量的初始化

  当你试图在声明中初始化变量时,类似的问题也会出现,因为局部变量实际上在仿真开始前就被赋了初值。常规的解决办法是避免在变量声明中赋予除常数外的任何值。对局部变量使用单独的赋值语句也会使控制变得更方便。

system verilog 队列删除 system verilog fork_system verilog 队列删除_03

 

   存在漏洞的地方是,变量local_addr是静态分配的,所以实际上在仿真的一开始它就有了初值,而不是等到 begin...end 块中才进行初始化。同样,解决的办法是把程序块声明为automatic。

五、时间值

1. 时间参数

  $timeformat 的四个参数分别是时间标度(-9代表纳秒,-12代表皮秒),小数点后的数据精度,时间值之后的后缀字符串,以及显示数值的最小宽度。eg:$timeformat(-9, 3, "ns", 8);

2. 时间精度

 

`timescale 1ns/1ps  // 1ns:代表时间单位;1ps:代表时间精度;

  $time的返回值是一个根据所在模块的时间精度要求进行舍入的参数,不带小数部分,而$realtime的返回值是一个带小数部分的完整实数。