SV学习记录(三)
目录3.1 过程语句3.2 task、function以及void函数3.3 task、function3.4 子程序参数C-style参数方向高级参数类型参数缺省值用名字传递参数常见错误3.5 子程序的返回返回语句从函数中返回一个数组3.6 局部数据存储自动存储变量初始化3.7 时间值时间单位和精度时间量时间和变量$time and $realtime3.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--); // 判断 j0 是否成立 $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(自动存储)属性的routineref参数的第二个好处是在任务里可以修改变量而且修改结果对调用它的函数随时可见。当你有若干并发执行的线程时这可以给你提供一种简单的信息传递方式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 1b1; (posedge bus.grant) bus.addr addr; // 等待来自存储器的数据 (posedge bus.enable) data bus.data; // 释放总线并等待许可 bus.request 1b0; (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参数缺省值下面的函数是想输出一个数组大小不确定中部分元素的和。lowhigh这些在定义的时候给了默认值如果缺省可以采用默认值。如果传入了用传入值但是ref不能缺省例 3.12 带缺省参数值的函数 function void print_checksum(ref bit [31:0] a[], input bit [31:0] low0, input int high-1); bit [31:0] checksum0; if (high-1||higha.size()) higha.size()-1; for (int ilow;ihigh;i) checksuma[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 a1, b2, c3, d4); $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都是reftask sticky (ref int array[50], int a, b); // a 和 b 都是 input3.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.dataexpect_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; ab;3.7 时间值时间单位和精度timeunittimeprecisiontimescale如果已经写了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返回值带小数部分