SV学习(2)——过程语句、函数和任务

  • 1. 过程语句
    • 1.1. 硬件过程块
    • 1.2. 赋值语句
    • 1.3. 控制语句
      • 1.3.1. for循环
      • 1.3.2. while循循环
      • 1.3.3. do-while循环
      • 1.3.4. repeat循环
      • 1.3.5. foreach循环
  • 2. 函数和任务
    • 2.1. 函数function
    • 2.2. 任务 task
    • 2.3. 子程序参数
    • 2.4. 子程序返回
    • 2.5. 自动存储
  • 3. 变量声明周期
  • 4. 补充

1. 过程语句

1.1. 硬件过程块

在SV中首先需要清楚哪些语句应该被放置于硬件世界,哪些程序应该被放置于软件世界;
为了区分硬件世界和软件世界,先引申出一个概念域(scope)。为了区分硬件设计、软件设计,我们将定义的软件变量或者例化的硬件其所在的空间称之为域;
module / endmodule,interface / endinterface可以被视为硬件世界,program / endprogram和class / endclass可以被视为软件世界。

always是为了描述硬件的行为,而在使用时需要注意哪些使用方式是时序电路描述,哪种使用方式事组合电路描述;
always中的 @(event …) 敏感列表是为了模拟硬件信号的触发行为,同学们需要正确对标硬件行为和always过程块描述。需要理解硬件行为的核心要素有哪些?
所以说,always过程块是用来描述硬件时序电路和组合电路的正确打开方式,因此只可以在module或者interface中使用;

always具备描述硬件电路行为的核心要素,你认为下列哪些事正确使用always的方式?

  • 由时钟驱动
  • 由其他非时钟信号驱动
  • 不同always语句块之间是并行执行的
  • 可以在always中初始化变量

always中,可以对对应的信号做复位,初始化变量是一个软件的概念,通常在定义变量的时候或者在initial中进行初始化。

initial从名字也看得出来,与always在执行路径上有明显区别,即initial非常符合软件的执行方式,即只执行一次;
initial和always一样,无法被延迟执行,即在仿真一开始它们都会同时执行,而不同initial和always之间的执行顺序上是没有顺序可言的,因此不应该将它们在代码中的前后顺序与它们的执行顺序画上等号;
initial从其执行路径的属性来看,它不应该存在于硬件设计代码中,它本身不可综合,对于描述电路没有任何帮助;
initial就是为了测试而生的,由于测试需要按照时间顺序的习惯,即软件方式来完成,所以initial便可以实现这一要求;

在Verilog时代,所有的测试语句都可以被放置在initial中,为了便于统一管理测试顺序,建议将有关测试语句都放置在同一个initial过程块中;
initial过程块可以在module、interface和program中使用;
对于过程块的书写方式,请记住用begin…end将其作用域“包”住,这一建议同样适用于稍后提到的控制语句、循环语句等;

1.2. 赋值语句

SV比verilog多了自加和自减操作符,允许变量自身可以将当前值做递增或递减。

assign优先级高于普通过程赋值语句,所以处于连续赋值状态的寄存器变量将忽略普通过程赋值语句对它的过程赋值操作,其逻辑状态仍然由过程连续赋值语句表达式做决定。使用assign对寄存器型变量赋值之后,这个值将一直保持在这个寄存器上,直至遇上deassign。

// assign_examplemodule test_assign ( );wire [ 9: 0] net_data [10: 0];initial    beginlogic  [ 9: 0] data    [10: 0] ;logic  [ 9: 0] var_data        ;data[0]    <=  'x;        // 非阻塞赋值data[1] =  'z;        // 阻塞赋值#5 data[2]   =  7;#5 data[2]++;           // 变量自加#5 data[2]--;            // 变量自减#5 assign var_data   =  data[2];    // 连续赋值语句将7赋值给var_data#5 var_data   =  4;              // 不会改变var_data地数值#5 deassign   var_data;           // 去除连续连续赋值地作用#5 var_data   =  5;              // var_data的值改变了#5 force    var_data    =  data[1];#5  var_data    =  8;#5 release    var_data;#5 var_data    =  9;  #5 force    net_data[1] =  10'h00;#5 release  net_data[1];endendmodule

1.3. 控制语句

1.3.1. for循环

SV允许在循环内产生一个本地变量,其他并行循环不会偶然地影响这个循环控制变量

 for (int i = 0, j = 0; i < 256; i++, j++)...

1.3.2. while循循环

举例,计算向量中1的个数

 // while_examplemodule test_while ( );logic [ 3: 0] count;logic [ 7: 0] temp;initial    begincount  = 0;temp   = 8'b0011_1100;#10while (temp)    beginif (temp[0] == 1'b1)count ++;// temp = temp >> 1;temp = {1'b0, temp[7:1]};end$display ("%d",count);endendmodule

1.3.3. do-while循环

逐位打印字符串

 // dowhile_examplemodule test_dowhile ( );//    int map [string];   // 关联数组,索引为string型bit    [ 7: 0] map [string];// map["hello"] = 1;//  map["sad"]   = 2;//  map["world"] = 3;//  // 对关联数组的初始化放在initial外部会有报错//   // near "[": syntax error, unexpected '[', expecting IDENTIFIER or TYPE_IDENTIFIERstring s;initial  beginmap["hello"] = 1;map["sad"]   = 2;map["world"] = 3;s = "hello!";if (map.first(s))do$display ("%8s: %2d", s, map[s]);while (map.next(s));endendmodule

1.3.4. repeat循环

通过加法和移位实现乘法器

 // repeat_examplemodule test_repeat ( );parameter   size        =  8;parameter longsize    =  16;logic    [size: 1]   opa = 3, opb = 6;logic    [longsize: 1]   result  ;initial    beginlogic  [longsize: 1]   shift_opa, shift_opb;shift_opa = opa;shift_opb = opb;result = 0;repeat (size)    beginif (shift_opb[1])result = result + shift_opa;shift_opa = shift_opa << 1;shift_opb = shift_opb >> 1;end$display ("The result is %d", result);endendmodule

1.3.5. foreach循环

foreach循环语句中指定数组后,程序会逐个遍历数组成员。

foreach ( <array name> [< loop variable>] ) <statement>

它的自变量可以是一个指定的任意类型数组(固定尺寸的 / 动态的及联合数组),然后紧跟着一个包围在方括号内的循环变量的列表。每一个循环变量对应于数组的某一维度。 foreach结构类似于一个repeat循环,它使用数组返回替代一个表达式来指定重复次数。

// foreach_examplemodule test_foreach ( );string words [2] = '{"hello", "world"};int       prod [ 1: 8][ 1: 3];initial begin// int j;foreach (words[j])$display (j, words[j]);foreach (prod[k ,m])prod[k][m] = k * m;endendmodule



循环变量的数目必须匹配数组变量的维数。空循环变量可以用来表示在对应的数组维数上没有迭代,并且处于尾部的连续空循环变量可以被忽略。循环变量是自动的、只读的,并且它们的作用范围对于循环来讲是本地的。每一个循环变量的类型被隐含的声明成与数组索引一致的类型。

2. 函数和任务

Verilog中要求函数必须有返回值,并且返回值必须被使用,例如用到赋值语句中;SV的限制稍微放宽了;

2.1. 函数function

  • 可以在参数列表中指定输入参数(input)、输出参数(output)、输入输出参数(inout)或者引用参数(ref);
  • 可以返回数值或者不返回数值(void);
function int double (input a);return 2*a;
endfunctioninitial  begin$display ("double of %0d is %0d", 10, doublr(10));
end

function属性:

  • 默认的数据类型是logic,例如 input [ 7: 0] addr;
  • 数组可以作为形式参数传递;
  • function可以返回或者不返回结果,如果返回即需要用关键词return,如果不返回则应该在声明function时采用void function ();
  • 只有数据变量可以在形式参数列表中被声明为ref类型,而线网类型则不能被声明为ref类型;
  • 在使用ref时,有时候为了保护数据对象只被读取不被写入,可以通过const的方式来限定ref声明的参数
  • 在声明参数时,可以给入默认数值,例如 input [ 7: 0] addr = 0,同时在调用时如果省略该参数的传递,那么默认值即会被传递给function;

2.2. 任务 task

任务相比于函数要更加灵活,且以下不同点:

  • task无法通过return返回结果,因此只能通过output、inout或者ref的参数来返回;
  • task内可以置入耗时语句,而function则不能。常见的耗时语句包括@event、wait event、# delay等;
    function是立即返回
task my_task (output logic [31: 0] x, input logic y);
...
endtask

通过上面的比较,我们对function和task建议的使用方法是:

  • 对于初学者傻瓜式用法即全部采用task来定义方法,因为它可以内置常用的耗时语句;
  • 对于有经验的使用者,请今后对这两种方法类型加以区别,在非耗时方法定义时使用function,在内置耗时语句时使用task。这么做的好处是在遇到了这两种方法定义时,就可以知道function只能运用于纯粹的数字或者逻辑运算,而task则可能会被运用于需要耗时的信号采样或者驱动场景;
  • 如果要调用function,则使用function和task均可对其调用;而如果要调用task,我们建议使用task来调用,这是一位内如果被调用的task内置有耗时语句,则外部调用它的方法类型必须为task;

出个题目:

typedef struct {bit  [ 1: 0] cmd;bit [ 1: 0] addr;bit    [31: 0] data;
} trans;function automatic void op_copy (trans t, trans s);t = s;
endfunctioninitial  begintrans  s;trans t;s.cmd = 'h1;s.addr = 'h10;s.data = 'h100;op_copy (t, s);t.cmd = 'h2;
end

上述代码,变量t中的三个成员{cmd, addr, data}最后数值为多少?

  • {'h1, 'h10, 'h100}
  • {'h2, 'h0, 'h0}
  • {'h2, 'h10, 'h100}
  • {'h0, 'h0, 'h0}

B:函数端口中,没有声明端口方向,默认都是输入,s和t都是input


2.3. 子程序参数

Verilog中,要求定义某些参数两次,方向、类型,任务名后不能出现输入、输出端口列表,

task my_task;output  [31: 0] x;reg       [31: 0] x;input         y;...
endtask

SV中,任务、函数声明的形式参数可以定义在圆括号中,参数缺省方向是input,缺省类型是logic

task my_task (output int x, input logic y);...
endtask

每一个形式参数可以选择的方向属性:
input:在开始的时候复制值
output:在结束的时候复制值
inout:在开始的时候复制,在结束的时候输出
ref:传递引用(句柄或者指针)

Verilog对于参数只有传值的方式:在子程序调用的时候,类型为input或者inout的实际参数的数值被复制到本地变量,在子程序退出的时候,类型为output或者inout的实际参数的数值被更新;
SV提供两种方式来为函数和任务传递参数:值传递和引用传递。引用传递(reference)像是C++的句柄或者指针,也就是变量的入口地址。此时,参数的类型应定义为ref

通过引用将数组传递到子程序中,

function void ary_sum (const ref int ary []);
// 引用传递,相对于指针传递
// 加const防止改变传递进来的变量的值
// 也就是只能读取变量,而不能写变量int sum = 0;for (int i = 0; i < ary.size; i++)sum += ary[i];$display ("The sum of the arrays is %0d", sum);
endfunction

通过传值的方式来传递数组参数,这样数组将被整体复制,这样消耗一定的内存和操作时间。而使用ref传递,只是获取该数组的入口地址(句柄 / 指针),操作速度快,减少内存使用。

const关键字可以防止一个函数或者任务改变一个通过ref类型传递的变量。这对于大型的数据结构通过ref传递,避免了整个结构被复制,而且在函数内部也不会被改变。一旦使用了const ref编译器就会检查子程序是否修改了该数组,当试图改变数组值时,编译器报错

在子程序修改ref参数变量的时候,其变化对于外部是立即可见的,在几个程序并行执行而想通过简单方法传递信息的情况下很有用,在并发执行线程中很有用。

下面的例子,bus.enable有效,初始化块中的thread2块马上就可以获取来自存储器的数据,而不用等到bus_read任务完成总线上的数据处理后返回,这可能需要若干个时钟周期。由于参数data是以ref方式引用传递的,所以只要任务里的data一有变化,@data语句就会触发,

// ref_examplemodule test_ref ( );logic  [31: 0] addr;logic  [31: 0] data;initial    forkbus_read (addr, data);begin@ data;$diaplay ("data is %0d", data);endjointask automatic bus_read (input logic [31: 0] addr, ref logic [31: 0] data);// request bus and drive addressbus.request = 1'b1;@ (posedge bus.grant)   bus.addr=  addr;// wait for data from memory@(posedge bus.enable) data    =  bus.data;// release bus and wait for grantbus.request = 1'b0;@(negedge bus.grant);endtaskendmodule

参数可以通过名字或者位置来传递。任务和函数的参数还可以指定缺省值,这就是使得调用任务或函数的时候可以不用传递参数。

function void print_sum (ref int a [ ]);...
endfunctionfunction void calculate ( );...print_sum (a);
endfunctioninitial  begincalculate ( );
end

若在后来需要修改print_sum函数参加参数,指定数组的开始地址和结束地址,

function void print_sum (ref int a [ ], input int start, input int last);

修改过函数后,原程序中热任何一个调用print_sum( )的代码,就需要对应改成print_sum (a1, b1, c2); 否则缺少了参数,程序无法正常运行。如采用参数默认值的方法,就不需要做改动了,

function void print_sum (ref int a [ ], input int start = 0, input int last = a.size() - 1);

下例是采用名字进行参数传递,

task many    (input  int a = 1, b = 2, c = 3, d = 4);$display ("%0d, %0d, %0d, %0d", a, b, c, d);
endtaskinitial  beginmany(6, 7, 8, 9);  // 6 7 8 9 指定所有值many();             // 1 2 3 4 使用缺省值many(.c(5));        // 1 2 5 4 只指定cmany( , 6, .d(8));   // 1 6 3 8 混合方式
end

建议用ref追踪外部变量的变化,如果添加const ref就表示这个数是只读的,不可以再修改还能追踪到实时的值

2.4. 子程序返回

在Verilog中,当任务、函数运行到endtask、endfunction的时候退出,由于任务中可以有延迟语句、敏感事件控制语句等事件控制语句,任务定义结构内可以出现disable终止语句,这条语句将中断正在执行的任务。在任务被中断后,程序流程将返回到调用任务的地方继续向下执行。而在SV中,可以使用return语句在任务体或函数体的任意一点退出子程序。

在Verilog中函数必须有返回值,返回值是用过对函数名赋值来完成的;而SV允许将函数声明成void类型,它没有返回值,其他函数就像Verilog一样,可以通过为函数名赋值来返回一个值,或者使用return语句实现,

// verilog
function [15: 0] my_func1 (input [ 7: 0] x, y);my_func1 = x * y - 1;   // 返回值赋值给函数名字
endfunction// SV
function [15: 0] my_func2 (input [ 7: 0] x, y);return x * y - 1;        // 使用return语句指定返回值
endfunction

SV中,任务和函数都可以用return返回

2.5. 自动存储

Verilog-1995 中,所有对象都是静态分配的,如果在测试程序里的多个地方调用同一个任务,由于任务里的局部变量会使用静态存储区,所以不同的线程之间会窜用这些局部变量;

Verilog-2001 通过使用automatic关键字,将任务、函数和模块声明为自动存储模式,这样,仿真器就能够对其所有形式的参数和内部变量使用堆栈的形式来存储。

automatic使函数和任务的重入(reentry)成为可能,

function automatic [63: 0] factorial;input   [31: 0] n;if (n == 1)factorial    =  1;elsefactorial =  n * factorial(-1);
endfunction

在SV中,子程序仍然默认使用静态存储方式,对于所有的模块(module block)和程序快(program block)也一样;对于声明在class中的子程序和变量默认使动态存储的。SV还允许在一个静态任务中将特定的形式参数和本地变量声明成自动的(automatic),也允许在一个自动任务内将特定的形式参数和本地变量声明成静态的(static)。

3. 变量声明周期

  • 在SV中,我们将数据的生命周期分为动态(automatic)和静态(static);
  • 局部变量的生命周期同其所在域共存亡,例如function/task中的临时变量,在其方法调用结束后,临时变量的声明也将终结,所以它们是动态生命周期;
  • 全部变量即便随这程序执行开始到结束一直存在,例如module中的变量默认情况下全部为全局变量,用户也可以理解为module中的变量由于在模拟硬件信号,所以它们是静态生命周期;
  • 如果数据变量被声明为automatic,那么在进入该进程/方法后,automatic变量会被创建,而在离开该进程/方法后,automatic变量会被销毁。而static变量在仿真开始时即会被创建,而在进程/方法执行过程中,自身不会被销毁,而且可以被多个进程和方法所共享;
// life_cycle_testmodule life_cycle_test ;function automatic int auto_cnt (input a);int cnt = 0;cnt = cnt + a;return cnt;endfunctionfunction static int static_cnt (input a);static int cnt = 0;cnt = cnt + a;return cnt;endfunctionfunction int def_cnt (input a);static int cnt = 0;cnt = cnt + a;return cnt;endfunctioninitial   begin$display ("@1 auto_cnt = %0d", auto_cnt(1));$display ("@2 auto_cnt = %0d", auto_cnt(1));$display ("@1 static_cnt = %0d", static_cnt(1));$display ("@2 static_cnt = %0d", static_cnt(1));$display ("@1 def_cnt = %0d", def_cnt(1));$display ("@2 def_cnt = %0d", def_cnt(1));endendmodule


automatic方法,内部的所有变量也是automatic,即伴随automatic方法的生命周期建立和销毁;

static方法,内部所有变量都是static类型,第一次调用的时候,声明static int cnt = 0; 计数器加1,返回1,第二次调用的时候,cnt是1,静态变量不会再次初始化,计数器加1,返回2;

对于automatic或者static方法,用户可以对其内部定义的变量做单个声明,使其类型被显式声明为automatic或者static;

对于static变量,用户在声明变量时应该同时对其做初始化,而初始化只会伴随它的生命周期发生一次,并不会随着方法被调用被多次初始化
def_cnt ( ),默认函数是static的,里面的变量也都是静态变量;

一般情况,我们调用函数都期待是auto_cnt ( )的样子,这时候就需要automatic关键字了

在module、program、interface、task和function之外声明的变量拥有静态的生命周期,即存在于整个仿真阶段,这同C定义的静态变量一致;

在module、interface和program内部声明,且在task、process或者function外部声明的变量也是static变量,且作用域在该块内;

在module、program和interface中定义的task、function默认都是static类型;

在过程块(task、function、process)定义的变量均跟随它的作用域,即过程块的类型。如果过程块为static,则它们也默认为static,反之亦然。这些变量也可以由用户显式声明为automatic或者static;

为了使得在过程块中声明的变量有统一默认的生命周期,可以在定义module、interface、package或者program时,通过限定词automatic或者static来区分。对于上述程序默认的生命周期类型为static;

下列域中的变量哪些默认是static即静态变量?

  • module中声明的变量
  • module中initial过程块中声明的变量
  • 在module中定义的function声明的变量
  • 在module中定义的task声明的变量

4. 补充


定义这个task,为什么不用声明端口和类型?
在定义任务的时候,默认是input logic的;
clk是直接在module里面的定义的变量,这是全局变量,作用域是整个模块,可以直接在task里使用


静态变量?生命周期?全局变量?作用域?

SV学习(2)——过程语句、函数和任务、变量声明周期相关推荐

  1. SV学习笔记—function函数和task任务

    目录 1.function/task 1.1 function函数 1.2 task任务及和function的不同点 1.function/task function和task基本用法一致,但是有几个 ...

  2. Go 学习笔记(70)— Go 变量声明、变量初始化、值类型变量赋值、指针类型变量赋值

    1. 变量声明 要单纯声明一个变量,可以通过 var 关键字,如下所示: var s string 该示例只是声明了一个变量 s,类型为 string,并没有对它进行初始化,所以它的值为 string ...

  3. 变量和函数的定义和声明

    2. 定义和声明 2.1. extern和static关键字 在上一节我们把两个程序文件放在一起编译链接,main.c用到的函数push.pop和is_empty由stack.c提供,其实有一点小问题 ...

  4. Python学习 Day28 JS函数(二)

    JS函数(二) (一)return关键字 关键字return一般结合函数一起使用.而且需要注意,这个关键字一般只能在函数体中使用 作用: 1.函数体中如果遇见关键字return,函数体后面语句不再执行 ...

  5. JavaScript函数声明前置与变量声明

    先看一个例子 func1(); // 输出:我是函数声明func2(); // 报错 console.log(a); // 输出:undefinedfunction func1() {console. ...

  6. php变量 声明提升,TypeScript:let和const变量声明

    在开始介绍let和const变量声明前,有必要先了解下JavaScript里的var变量声明. var变量声明 全局声明 var声明在函数体外,所声明的变量为全局变量.var name = " ...

  7. ES5和ES6中的变量声明提升

    ES5和ES6中的变量声明提升 Example1: a=2; var a; console.log( a ); //结果为2 Example2: console.log( a ); //结果是unde ...

  8. C语言学习笔记10-指针(动态内存分配malloc/calloc、realloc、释放free,可变数组实现;Tips:返回指针的函数使用本地变量有风险!;最后:函数指针)

    C语言:指针 1. 指针:保存地址的变量 *p (pointer) ,这种变量的值是内存的地址.   取地址符& 只用于获取变量(有地址的东西)的地址:scanf函数-取地址符   地址的大小 ...

  9. Go 学习笔记(26)— Go 习惯用法(多值赋值,短变量声明和赋值,简写模式、多值返回函数、comma,ok 表达式、传值规则)

    1. 多值赋值 可以一次性声明多个变量,并可以在声明时赋值,而且可以省略类型,但必须遵守一定的规则要求. package main import "fmt"func main() ...

最新文章

  1. python使用xlrd模块读写excel
  2. openwrt固件_openwrt固件
  3. redis java 发布订阅_Redis之发布订阅(Java)
  4. BZOJ1057 [ZJOI2007]棋盘制作 【最大同色矩形】
  5. 2.4G无线音箱四层PCB设计学习(一)
  6. 100天python、github_GitHub - 1977950729/Python-100-Days: Python - 100天从新手到大师
  7. linux 批量删除任务,Linux-Shell脚本学习心得之批量创建、删除用户
  8. Dichotomy poj River Hopscotch
  9. 普乐蛙大型5d动感影院4d影院设备价格4d动感影院座椅
  10. EOS核心仲裁论坛 | 保障你财产安全的应急措施
  11. 前端开发精华网站(强烈推荐!)
  12. 门禁系统远程无线联网解决方案
  13. node.js毕业设计安卓电子阅读器APP(程序+APP+LW)
  14. 晶体三极管的放大原理
  15. 2019年中国金融信息化软件开发企业排名
  16. shell脚本(六)sed命令行编辑器
  17. 根据url导出pdf文件
  18. 图形图像处理-之-任意角度的高质量的快速的图像旋转 上篇 纯软件的任意角度的快速旋转
  19. 1.6 Cubemx_STM32F103_NOOS SDIO_DMA_FATFS基于SD卡的FATFS测试(一)
  20. ElasticSearch的Ingest节点

热门文章

  1. VMware绕过软件的虚拟机检测
  2. for example: not eligible for auto-proxying
  3. android学习笔记(2)--RxJava
  4. linux如何管理进程,在嵌入式linux中进程是怎么管理的
  5. Praat脚本-029 | 一种更有效的校对音频内容的方案
  6. JS 解决sort字母排序的问题
  7. 2017.11-上海商泰汽车有限公司面试
  8. Linux系统怎么使用扫描仪,Linux系统中Nmap扫描命令的使用方法 -电脑资料
  9. 如何为Apache JMeter开发插件(三)——冲破图片验证码的束缚
  10. crash linux主要命令,kdump和crash的配置方法以及故障分析方法