目录
3.1 过程语句
3.2 task、function以及void函数
3.3 task、function
3.4 子程序参数
C-style
参数方向
高级参数类型
参数缺省值
用名字传递参数
常见错误
3.5 子程序的返回
返回语句
从函数中返回一个数组
3.6 局部数据存储
自动存储
变量初始化
3.7 时间值
时间单位和精度
时间量
时间和变量
$time and $realtime
3.1 过程语句
sv从C中引入了一些特点。
如下,可以在循环中定义循环变量,这在v中好像是不行的。
例 3.1 新的过程语句和操作符 initial begin: example integer array[10], sum, j; // 在 for 语句中声明 i for (int i = 0; i < 10; i++) // i 递增 array[i] = i; // 把数组里的元素相加 sum = array[9]; j = 8; do // do...while 循环 sum += array[j]; // 累加 while (j--); // 判断 j=0 是否成立 $display("Sum=%4d", sum); // %4d-指定宽度 end : example // 结束标识符还有continue和break,这个v中似乎也有。
例 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; // Done——终止并跳出循环 ... // 此处处理其他命令 endcase // case(cmd) end $fclose(file); end3.2 task、function以及void函数
v中,task可以消耗时间,但是function不行。function必须有返回值
sv中function可以调用task,但是只能在fork...join_none中。
如果有不消耗时间的task,最好定义为void function,方便复用。
例 3.3 用于调试的 void 函数 function void print_state(...); $display("@@t: state=%s", $time, cur_state.name()); endfunction3.3 task、function
现在只要有task...endtask和function...endfunction就可以
例 3.5 不带 begin...end 的简单任务 task multiple_lines; $display("First line"); $display("Second line"); endtask : multiple_lines3.4 子程序参数
C-style
端口声明可以一次声明方向+类型,现在V也行了,估计这书比较老。
例 3.7 C语言风格的子程序参数 task mytask1 (output logic [31:0] x, input logic y); ... endtask
参数方向
如果声明端口的时候忘了加方向和类型,sv中默认为input logic
不建议这么干
例 3.9 带缺省类型的子程序参数 task T3(a, b, output bit [15:0] u, v);
高级参数类型
旧v不能传递数组,现在sv可以
ref类似C中的指针,传的时候不是把整个数组复制过去,而是让它能够拿来使用就可以。如果不写ref,就会复制。
const使得function只能使用它的值而不能修改。
例 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但是ref只能用于带automatic(自动存储)属性的routine
ref参数的第二个好处是在任务里可以修改变量而且修改结果对调用它的函数随时可见。当你有若干并发执行的线程时,这可以给你提供一种简单的信息传递方式
task的data只写了ref没有注明方向,这是因为写ref就表明会对data这个变量直接进行操作。sv中有四个方向:input, output, ref, inout
在initial块中,有两个线程,分别是task和thread,因为了写了ref data,所以两个线程能一起检测到变化。
例 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参数缺省值
下面的函数是想输出一个数组(大小不确定)中部分元素的和。
low,high这些在定义的时候给了默认值,如果缺省可以采用默认值。如果传入了用传入值
但是ref不能缺省
例 3.12 带缺省参数值的函数 function void print_checksum(ref bit [31:0] a[], input bit [31:0] low=0, input int high=-1); bit [31:0] checksum=0; if (high==-1||high>=a.size()) high=a.size()-1; for (int i=low;i<=high;i++) checksum+=a[i]; $display("The array checksum is %0d",checksum); endfunction 例 3.13 使用参数的缺省值 print_checksum(a); // a[0:size()-1]中所有元素的校验和——缺省情况 print_checksum(a,2,4); // a[2:4]中所有元素的校验和 print_checksum(a,1); // 从 1 开始 print_checksum(a,2); // a[0:2]中所有元素的校验和 print_checksum(); // 编译错误: a 没有缺省值用名字传递参数
和v中例化的思想一样,没什么新的。
例 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 // a b c d many(6, 7, 8, 9); // 6 7 8 9 指定所有值 many(); // 1 2 3 4 使用缺省值 many(.c(5)); // 1 2 5 4 只指定 c many(6, .d(8)); // 1 6 3 8 混合方式 end常见错误
如果写task时,省略了后面参数的方向,那么会默认和前一个一样。
对于下面情况,ab都是ref
task sticky (ref int array[50], int a, b); // a 和 b 都是 input
3.5 子程序的返回
返回语句
sv里有return,用法和C类似
例 3.18 在任务中用 return 返回 task load_array(int len, ref int array[]); if (len <= 0) begin $display("Bad len"); return; end // 任务中其余的代码 endtask从函数中返回一个数组
方法一:定义一个数组类型,在函数声明中使用这个类型
fixed_array5是包含五个int元素的数组类型。
例 3.20 使用 typedef 从函数中返回一个数组 typedef int fixed_array5[5]; fixed_array5 f5;//等价于 int f5[5] function fixed_array5 init(int start);//函数的返回值是int[5] foreach (init[i])//在函数内部,函数名本身就是一个代表“返回值”的隐式局部变量,所以可以用 init[i] = i + start; endfunction initial begin f5 = init(5); foreach (f5[i]) $display("f5[%0d]=%0d", i, f5[i]); end问题:如果数组太大,可能放不下
方法二:通过ref传递参数
例 3.21 把数组作为 ref 参数传递给函数 function 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("fa[%0d]=%0d", i, fa[i]); end方法三:把函数包装到类中,返回对象的句柄
3.6 局部数据存储
自动存储
意义:程序中的变量在堆栈上动态分配,每次调用都创建全新的副本,互不干扰。如果没有自动存储,并行调用的时候,会出现覆盖问题。
必须显式标注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
变量初始化
问题解释如下:sv中变量默认是静态的,加了automatic才是动态的。对于静态变量,在仿真开始(0s)的时候会初始化这个变量,如果这么写 logic [7:0] local_addr = addr << 2,是把initialize和declaration放到一起了,但是这种语句的initialize会在0s被执行,采集到addr为X赋给local_addr,之后再执行到这个语句的时候,不会再对local_addr赋值,因为它是静态的
例 3.23 静态初始化的漏洞(有漏洞版本) program initialization; // 有漏洞的版本 task check_bus; repeat (5) @ (posedge clock); if (bus_cmd == 'READ) begin // 何时对 local_addr 赋初值? logic [7:0] local_addr = addr << 2; // 有漏洞 $display("Local Addr = %h", local_addr); end endtask endprogram解决方法:program后标注上automatic;或把declaration和initialize分开,logic a; a=b;
3.7 时间值
时间单位和精度
timeunit+timeprecision=timescale
如果已经写了timescale,注意要在模块内部定义timeunit和timeprecision覆盖。
时间量
$timeformat(-9,3,"ns",8);这一句话的理解:-9表示以s为单位,乘10^(-9);3表示小数点后三位;ns表示在数字后面额外打印的内容;8表示输出的最小宽度为8个字符。这句话规定了输出格式。
例 3.26 时间参数和 $timeformat module timing; timeunit 1ns; timeprecision 1ps; initial begin $timeformat(-9,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时间和变量
注意time是64bit整型,所以会舍入。
# rdelay;就算是浮点,在用于时间延迟的时候,也会被舍入。
例 3.27 时间变量及舍入 `timescale 1ps/1ps module ps; initial begin real rdelay = 800fs; // 以 0.800 存储 time tdelay = 800fs; // 舍入后得到 1 $timeformat(-15, 0, "fs", 5); # rdelay; // 时延舍入后得到 1ps $display("%t", rdelay); // "800fs" end endmodule系统任务$time的返回值是一个根据所在模块的时间精度要求进行舍入的整数,不带小数部分;$realtime返回值带小数部分