程序块

1.程序块#

模块是Verilog中的基本构建块,非常适用于设计。然而,对于测试平台来说,需要花费大量精力来正确初始化和同步环境,避免设计和测试平台之间的竞赛,自动生成输入刺激,并重用现有模型和其他基础设施。System Verilog增加了一种新的块类型,称为程序块。它使用program和endprogram关键字声明。

语法
program program_name(port_list);
initial begin
.....
end
endprogram

由于程序结构提供了无竞赛的交互,程序块内部定义的所有项目将在反应区中执行。程序块内的initial块计划在反应区执行,而模块内的非阻塞赋值则计划在活动区并在NBA区分配值。

这个反应区集包括:

image

依赖于设计信号变化的程序块内的语句将在反应区调度。通过在反应区之前调度活动区,可以避免测试平台和设计之间的竞赛条件。 -> 它不允许always块。只允许initial和方法,这些更可控。 -> 每个程序可以通过调用$exit系统任务显式退出。不像$finish,它会立即退出仿真,即使有未处理的事件。 -> 就像模块一样,程序块也有端口。可以在顶层网表中实例化一个或多个程序块,并与DUT连接。

以下示例展示了基于模块的测试平台和基于程序的测试平台之间的区别。

代码片段

module DUT();
  reg a = 0;
  initial begin
    a<= 1;
  end
endmodule

module TB_using_Module();
  initial begin
    $display("Module_based_TB : a = %b\n", DUT.a);
  end
endmodule

program TB_using_Program();
  initial begin
    $display("Program_based_TB : a = %b\n", DUT.a);
  end
endprogram

输出

program

如果我们观察到,在模块中它给出了0的值,但在程序块中,a的值为1。因为在活动区中已经为’a’分配了值。

2.动态类型转换#

SystemVerilog的类型转换是指将一种数据类型转换为另一种数据类型。

它有两种类型: i. 静态类型转换
ii. 动态类型转换

静态类型转换不适用于面向对象编程(OOP),因此我们使用动态类型转换来进行OOP。它在运行时进行。

使用$cast关键字可以实现动态类型转换。这个$cast可以是一个函数或一个任务。

语法

$cast(destination, source);

代码片段

class hyd;
  string a;
  int d=8;
  function display();
    $display("a = %0s",a);
    $display("d=%0d",d);
  endfunction
endclass

class branch extends hyd;
  string b;
  function display();
    super.display();
    $display("b = %0s",b);
  endfunction
endclass

module casting;
   hyd p;
   branch  c;
   branch c1;
     
   initial begin:BEGIN_I
      c = new;
      c.a = "charminar";
      c.b = "cafe";
      p = c;
      $cast(c1,p);
      $display("contents of c1");
      c1.display();
   end:BEGIN_I

endmodule:casting

输出

dynamic casting

3.文件操作#

SystemVerilog允许我们在系统或磁盘中读写文件。

我们将在SystemVerilog中进行不同的文件操作

速查表

序号系统函数描述
1.$fopen()使用$fopen()系统任务可以打开一个文件进行读或写操作。此任务将返回一个称为文件描述符的数据类型句柄。
2.$fclose()使用$fclose()系统任务关闭文件描述符(句柄)。
3.$fdisplay()这是一个系统任务,用于将内容添加到文件中。
4.$fgetc()这是一个系统任务,用于从文件中读取字符(字节)。
5.$ungetc()这是一个系统任务,用于将字符插入文件中。
6.$fgets()这是一个系统任务,用于从文件中读取一行。
7.$fscanf()用于从文件中读取数据。
8.$sscanf()sscanf从给定变量中读取数据。
9.$rewind()$rewind可用于更改文件中的当前读写位置。
10.$feof()SystemVerilog有一个名为$feof()的任务,当文件到达末尾时返回true。
11.$readmemb()readmemb任务读取二进制数据。
12.$readmemh()readmemh任务读取十六进制数据。
13.$sformat()
14.$sformatf()

$fopen()#

使用$fopen()系统任务可以打开一个文件进行读或写操作。此任务将返回一个称为文件描述符的数据类型句柄。应使用此句柄来读取和写入该文件,直到文件被关闭。

我们将在文件操作中使用读取、写入和追加模式。

工作模式速查表

参数描述
“r”读取模式打开
“w”写入模式创建文件,若文件存在则覆盖
“a”若文件不存在则创建文件,否则追加;在文件末尾以写入模式打开
“r+”更新模式打开(读取和写入)
“w+”截断或创建用于更新
“a+”追加,在文件末尾打开或创建用于更新

语法
datatype file_handle;
file_handle = $fopen("filename", "working_mode");

$fclose()#

使用$fclose()系统任务关闭文件。

语法
$fclose(filehandle);

$fdisplay()#

这是一个系统任务,用于将内容添加到文件中。

语法
$fdisplay(filehandle, "content to add into file");

代码片段

module file_handles;
int f;
   initial begin
      f=$fopen("file_handle","w");
      
      $fdisplay(f,"fileoperations");
      $fdisplay(f,"sv course");

      $fclose(f);
   end 
endmodule

输出

example

$fgetc()#

这是一个系统任务,用于从文件中读取一个字符(字节)。

语法$fgetc(filehandle);

$ungetc()#

这是一个系统任务,用于将字符插入文件中。

语法$ungetc(c, f);

$fgets()#

这是一个系统任务,用于从文件中读取一行。

语法$fgets(variable, filehandle);

代码片段

module f_get;
   int c;
   int f;
   string line;
   int dummy;

   initial begin
      
      //write operation
      f=$fopen("file","w");
     
      $fdisplay(f,"sv course");
      $fdisplay(f,"filehandling");
     
      $fclose(f);
      
      //read operation
      f=$fopen("file","r");
      $display("");
      
      c = $fgetc(f);
      $display("reading one character : %0s",c);

      dummy = $ungetc(c,f);
      $display("character insert : %0s",c);

      dummy = $fgets(line,f);
      $display("line read : %0s",line);
     
      $fclose(f);
   end
endmodule

输出

fil_get

$fscanf()#

用于从文件中读取数据。

语法
$fscanf(filehandle, "variable = format_specifier", variable);

示例

$sscanf()#

sscanf从给定变量中读取数据。

语法
$fgets(variable, filehandle);
$sscanf(variable, %0s, variable1);

代码片段

module scanf;
 
   int fd;
   int i;
   string str,str2;
   int dummy;
 
   initial begin

      fd = $fopen ("hihihi.sv", "w");

      $fdisplay(fd,"bhavana");
      $fdisplay(fd,"Teams");
      $fdisplay(fd,"BJT");

      $fclose(fd);

      fd = $fopen("hihihi.sv","r");
      
      //Usage of fscanf
      dummy = $fscanf(fd,"str=%0s",str);
      repeat(2)
      begin
         dummy = $fgets(str,fd);
         $display("");
         $display("contents of fscanf");
         $display("str = %0s",str);
      end

      //Usage of sscanf
      $display("contents of sscanf");

      dummy = $fgets(str,fd);
      dummy =  $sscanf(str,"%0s",str2);
      
      $display("str = %0s",str);
      $display("str2 = %0s",str2);
      $display("");
      
      $fclose(fd);
   end
endmodule

输出

scanf

$sformat()#

此系统任务用于使用特定内容更新变量。

语法data_type var1, var2; $sformat(var2, "content", var1);

$sformatf()#

这是一个系统函数,将更新后的内容返回给变量。

语法data_type var1, var2; var2 = $sformatf("content", var1);

代码片段

module format;

   int a=9;
   int fd;
   string b="hii";
   string c;
  
   initial begin:BEGIN_I
  
      fd=$fopen("file","w");

      c = $sformatf("delta_%0d",a);
      $display("c=%0s",c);
  
      $sformat(b,"delta_%0d",a);
      $display("b=%0s",b);

      $fclose(fd);

   end:BEGIN_I
endmodule:format

输出

file_format

$feof#

SystemVerilog有一个名为$feof()的任务,当文件到达末尾时返回true。

语法$feof(filehandle);

$rewind#

$rewind可用于更改文件中的当前读写位置。我们在"w+“模式下使用$rewind将写位置重置到起始位置。

语法$rewind(filehandle);

代码片段

module tb;

   int fd;
   string str1,str2;

   initial begin

      //open the file in the write mode
      fd = $fopen ("hihihi.sv", "w+");

      //displays the contents into the file
      $fdisplay(fd,"teams");
      $fdisplay(fd,"c");
      $rewind(fd);

      //close the file
      $fclose(fd);

      //open the file in read mode
      fd = $fopen("hihihi.sv","r");
      $feof(fd);
      fd =$fopen("hihihi.sv","r");
    
      $fgets(str1,fd);
      $display("line:%0s",str1);
      
      $fclose(fd);

   end
endmodule

输出

rewind

$readmemb()#

readmemb()任务读取二进制数据。

语法$readmemb("filename", mem);

$readmemh()#

readmemh()任务读取十六进制数据。

语法$readmemh("filename", mem);

代码片段

module readmem;

   int fd;
   int i;
   int mem[3];
   string str;

   initial begin:BEGIN_I
      //open the file in write mode
      fd = $fopen ("hihihi.sv", "w");

      $fdisplay(fd,10000);
      $fdisplay(fd,11011);
      $fdisplay(fd,"c");

      //close the file in write mode
      $fclose(fd);

      $readmemh("hihihi.sv",mem);
      $readmemb("hihihi.sv",mem);
      repeat($size(mem))

      begin:BEGIN_II
         $display(""); 
         $display("hexadecimal[%0d] = %0h",i,mem[i]);
         $display("Binary[%0d] = %0b",i,mem[i]);
         $display("");
         i++;
      end:BEGIN_II

   end:BEGIN_I
endmodule:readmem

输出

output-readmem

4.包#

包是一组可共享的函数、任务、类。包旨在实现代码的可重用性,类似于函数、任务和类,但所有这些都在一个实体下。

image

  • 在包中我们可以包含类、函数和任务,但不能包含模块。
  • 如果需要在多个模块中使用相同的类、函数或任务,或者全部,而不需要一次又一次地编写它们,我们只需将它们全部放在一个实体下,即包,通过导入该包,我们可以在模块中的任何地方使用它们。

语法

package <package_name>;

endpackage:<package_name>

  • 要在任何模块中使用包,我们应该使用**import关键字后跟package_name**来导入包。

语法:

import <package_name>::<method_name>;
import <package_name>::*; //导入所有内容

代码片段

包代码

package one;

  int a;
  string k;

  class details;
    
    int age;
    string name;
    
    function new(int a,string b);
      age=a;
      name=b;
    endfunction
    
    function void getdetails();
      $display("name is  %0s",name," ,age is %0d",age);
    endfunction
    
    task t1;
      $display("it is in task of class");
    endtask: t1
    
    function void hi;
      $display("hi");
    endfunction
  
  endclass
  
  function void pack_func;
    details d;
    d=new(40,"raj");
    a=d.age;
    k=d.name;
    $display("in package function");
    $display("name given is %0s",k,", age is %0d",a);
  endfunction

  task pack_task;
    input int a;
    output string k="it is odd";
    if(a%2==0)begin
      k="it is even";
    end
  endtask

endpackage : one  

在上面的代码中,包one包含一个名为details的类,其中包含几个函数。还有一个在包pack_func中的函数和一个任务pack_task

包内的代码只能被编译。

模块代码

import one::*;

module mod1;

  string id1;
  details emp1=new(21,"kumar");

  initial begin
    string g;
    $display("details of id1 are : %0d",emp1.age);
    emp1.getdetails();
    emp1.t1();
    pack_func();
    pack_task(10,g);
    $display(g);
  end
endmodule : mod1 

输出

image

在模块代码中,我们想要使用包one中的类details,为此我们使用import关键字导入包。这意味着包中编写的所有代码现在都是模块的一部分,并且可以被访问。这里类句柄emp1用于访问类属性和类方法。包函数和任务可以像模块函数和任务一样被访问。

关于包的更多信息#

  • 我们可以在单个文件中拥有多个包。
  • 包不能是static、extern、virtual、pure。
  • 我们可以在包内导入另一个包,但不能在包内编写包。
  • 如果两个包具有相似的函数,则我们使用包名称,后跟作用域解析运算符来访问该函数。
  • 包不能在类的范围内导入,但可以在函数内导入,并且在该函数内是有效的。
  • 包可以导入到另一个包中,但导入的包必须在使用之前被编译。
  • 如果两个或更多的包具有相同的名称,那么编译器将采用最近编译的包。
  • 在文件中导入包意味着指向该包,其范围仅对导入它的文件可见,这意味着让我们在pkg P1中导入P2,我们会认为导入P1使我们能够使用P2的内容,但事实并非如此,因为P2的范围在P1中结束。要再次使用P2的内容,只需通过导入p1使用export
    语法:export P2;

5.作用域解析运算符#

作用域解析运算符表示为**::**,用于在类的范围内引用标识符。作用域解析运算符的左侧必须是类类型名称、包名称、typedef名称,右侧应该是任何方法名称(任务/函数)的变量名称。

在System Verilog中,作用域解析运算符**::**在以下情况下使用:

1.定义外部类方法

借助作用域解析运算符,我们可以在类的主体外定义一个类方法

代码片段

class my_class;
  
  int a=5;
  int b=10;
  
  extern function void sum();
  extern function void sub();
  
endclass:my_class
 
function void my_class::sum();
  int result1;
  result1=a+b;
  $display("After summation the result1 is =%0d",result1);
endfunction
 
function void my_class::sub();
  int result2;
  result2=b-a;
  $display("After subsration the result2 is =%0d",result2);
endfunction

module extern_exm;
 
  initial begin:BEGIN_I
  
    my_class A1;
    A1=new();
    
    A1.sum();
    A1.sub();
  
  end:BEGIN_I
 
endmodule:extern_exm

输出

在上面的代码中,借助作用域解析运算符,我们在类的外部定义了sum和sub类函数,然后在模块内创建对象后,我们访问了这两个方法,然后显示结果1=15和结果2=5。

Untitled Diagram drawio

2.访问静态方法/属性

使用作用域解析运算符在类的外部访问类的静态成员。

代码片段

class my_class;
     
  int a;
  static int b;
 
  function void display();
    $display("value of a=%0d",a);
    $display("value of b=%0d",b);
  endfunction
 
endclass:my_class
 
module static_exm;
 
  initial begin:BEGIN_I
    my_class A1;
    A1 =new();
    A1.a=10;
    $display("Using the scope resolution operator we can access the static properties");
    my_class::b=20;   
    A1.display();
  end:BEGIN_I
 
endmodule:static_exm  

输出

在上面的代码中,借助作用域解析运算符,我们在模块内访问了类的静态属性(b),在模块中为a和b都赋值,因此显示a的值=10和b的值=20。

static-Page-4 drawio

3.在模块中访问未导入的包参数

在不导入包的情况下,我们可以通过使用作用域解析运算符来访问包参数。

代码片段

package my_pkg;

  int a= 5;
 
endpackage:my_pkg
 
module pkg_exm;
 
  int b;
  
  initial begin:BEGIN_I
    b=my_pkg::a;      
    $display("Assining the value of a (properties of package) into b without importing package");
    $display("b=%0d",b);
  end:BEGIN_I
 
endmodule:pkg_exm  

输出

在上面的代码中,我们在不导入包的情况下,在模块内使用作用域解析运算符访问了包属性,这里将包属性a赋值给b,所以显示b=5。

pkg-Page-2 drawio

4.避免名称空间冲突

如果包或类的属性和模块变量相同,则借助作用域解析运算符可以区分它们。

代码片段

package my_pkg;

  int a=5;
 
endpackage :my_pkg
 
module pkg_exm;

  int a=10;
  int b=20;
  int c,d;
 
  initial begin:BEGIN_I
    $display("value a (package parameter) =%0d and  value of a (module variable)=%0d",my_pkg::a,a);
    c =my_pkg::a+b;
    d=a+b;
    $display("value c=%0d",c);
    $display("value d=%0d",d);
  end:BEGIN_I
 
endmodule:pkg_exm

输出

在上面的代码中,我们显示了定义在包中且未导入的属性a的值=5,以及定义在模块中的属性a的值=10。我们将a和b的和赋值给c,其中a是包属性,并将a和b的和赋值给d,其中a是模块变量。

avoid-Page-3 drawio

6.命令行输入#

命令行参数用于当我们希望避免测试台重新编译时。顾名思义,我们可以在命令行中给出程序的值。通过命令行传递的参数可以在我们的SV代码中通过名为plusargs的系统函数来访问。

Plusargs#

$test$plusargs#

当需要从命令行获得一些指令时,可以使用此函数,准确地说是一个字符串,以执行接下来的代码行。

值得注意的是,函数中提到的字符串的所有字符都应与我们在命令行中在+后提供的plusarg匹配。当字符串匹配时,函数$test$plusargs返回1,否则返回0。

语法

$test$plusargs("string")

模拟命令

vsim module_name -c -do "run -all;quit" +string

**注意:**我们在命令行界面中使用Mentor Questa模拟器,这解释了上述命令格式。

代码片段 1

module CLI_testargs;
  bit x;
  
  initial begin:BEGIN-I
    x=$test$plusargs("START");
    $display("$test$plusargs returns %d",x);
    if(x)
      $display("Start process");
    else
      $display("exit");
  end:BEGIN-I

endmodule

仿真命令:

vsim CLI_testargs -c -do "run -all;quit" +START

-> 在上面的代码片段中,如果我们在命令行中给出START,那么我们将得到x=1,并显示Start process

$test$plusargs returns 1  
Start process

-> 下面的代码利用了$test$plusargs在initial块的并行执行中的用法。

代码片段 2

initial begin:BEGIN_I
  x=$test$plusargs("START");
  $display("@%0dns In first begin block",$time);
  $display("@%0dns $test$plusargs returns %d",$time,x);
  if(x)
    $display("@%0dns Start process",$time);
  else
    $display("@%0dns exit",$time);
end:BEGIN_I

initial begin:BEGIN_II
  x=$test$plusargs("START");
  $display("@%0dns In second begin block",$time);
  $display("@%0dns $test$plusargs returns %d",$time,x);
  if(x)
    $display("@%0dns Start process",$time);
  else
    $display("@%0dns exit",$time);
end:BEGIN_II

仿真命令:

vsim CLI_testargs2 -c -do "run -all;quit" +START

@0ns In first begin block
@0ns $test$plusargs returns 1
@0ns Start process
@0ns In second begin block
@0ns $test$plusargs returns 1
@0ns Start process

-> 由于字符串START对于两个块都可用,因此函数在相同的时间戳中同时返回1。

$value$plusarg#

当我们需要通过命令行从用户那里获取输入值时,并且这些值可以在代码中进一步使用或修改时,可以使用此函数。

在模拟(命令行中),我们应该显式地给出值为_string=value_,它遵循+。

语法

$value$plusargs("string=format_specifier", variable_name)

这里,format_specifier可以是%d、%s等。
我们给出的_value_存储在variable_name中,并且可以在代码中访问。

模拟命令

vsim module_name -c -do "run -all;quit" +string=value

代码片段 1

module CLI_valargs;
  bit x;
  int y;
  string message;

  initial begin:BEGIN_I
    x=$value$plusargs("msg=%s",message);
    $display("$value$plusargs used above returns %0d",x);
    $display(message);
    void'($value$plusargs("value=%d",y));
    y+=1;
    $display("Incremeneted value of y:%0d",y);
  end:BEGIN_I

endmodule

-> 在上面的代码中,我们试图在命令行中给出两个值参数(一个是字符串,另一个是整数)。

模拟命令

vsim CLI_valargs -c -do "run -all;quit" +msg=HEY! +value=2

$value$plusargs used above returns 1
HEY!
Incremeneted value of y:3

代码片段 2

module CLI_valargs1;
  bit x;
  string y;
  string s;
  int fd,f;
  string message;
  
  initial begin
    x=$value$plusargs("msg=%s",message);
    $display("$value$plusargs used above returns %0d",x);
    $display(message);
    void'($value$plusargs("file=%s",y));
    fd=$fopen(y,"r");
    
    $fgets(s,fd);
    $display(s);
    
    $fclose(fd);

    fd=$fopen(y,"a");
    
    $fdisplay(fd,"Hurray!");
    
    $fclose(fd);
  end

endmodule

-> 在这里,我们试图将从命令行获取的内容添加到文件中。

cl1-Page-2 drawio

仿真命令

vsim CLI_valargs1 -l "CLI_valargs1.log" -c -do "run -all;quit" +msg=HEY! +file="sample.txt"

$value$plusargs 上面使用的返回1
嘿!
我们正在尝试使用命令行参数打开此文件,而且成功了!

</p>

</details>   

![cl1-Page-1 drawio](https://user-images.githubusercontent.com/110398433/195772488-6cf48d2d-d9ff-476a-94dd-83d399e90b9f.png)


> **注意**:$test$plusargs 和 $value$plusargs 这两个函数都是大小写敏感的。