Verilog常用语法

  该内容均可以在夏宇闻老师的《Verilog数字系统设计教程》第四版中找到,在此处只是便于回顾而已,没有书的可以参考,FPGA设计常用的都已经标出来了,有部分常用,但根C语言差不多的就没有标出来,有时间的可以仔细看一遍。

一、模块的结构、数据类型、变量和基本运算符号

1 模块的结构

1.1 端口定义

        module 模块名(口1,口2,口3,......);

  两种模块例化方式:

    方法一:模块名(连接端口1信号名,连接端口2信号名,连接端口3信号名,…);

    方法二:模块名(.端口名1(连接信号1名),.端口名2(连接信号2名),…);

  例化时还可以对模块中的参数型(parameter)变量进行重新赋值,例如:

	module min(input clk,input rst_n,input a1,output a2);
		parameter		a = 1000;
		parameter		b =  2000;
	.....
	endmodule

  对以上模块进行例化并修改a,b值

    min #(.a(10),.b(10)) uut_min(.clk(clk),.rst_n(rst_n),.a1(a1),.a2(a2));

  在模块uut_min中a与b的值就都为10了,所以参数化设计在例化时能很方便的对参数进行修改,而不需要修改原模块内容;

  方法一必须严格按照模块定义的端口顺序来连接,方法二不必严格按照端口顺序对应,一般使用第二种例化方式,减小错误;

1.2 模块内容

1.2.1 I/O说明的格式

  输入口说明:

		input[信号位宽-1:0] 端口名1;
		input[信号位宽-1:0] 端口名2;

   输出口说明:

   	output[信号位宽-1:0] 端口名1;
   	output [信号位宽-1:0] 端口名2;

  输入/输出口

		inout[信号位宽-1:0] 端口名1;
		inout[信号位宽-1:0] 端口名2;
1.2.2 内部信号说明

  wire和reg类型变量的声明

		reg[width-1:0] R变量1,R变量2,…;
		wire[width-1:0] W变量1,R变量2,...;

  原则说明:由always和initial块产生的信号,定义为reg型,其余均定义为wire型;

1.2.3 功能定义

  1.用assign连续赋值语句,常用来描述组合逻辑电路;如assign = a & b

  2.用实例元件;如与门:and #2 u1 (q,a,b)

  3.用always块,既可用来描述组合逻辑电路也可用来描述时序逻辑电路,常用来描述时序逻辑电路;在“always”模块内被赋值的每一个信号都必须定义成reg型,进行组合逻辑描述时,敏感列表可以直接用@(*)表示,防止敏感事件过多而写掉

  在Verilog模块中所有过程块、连续赋值语句、实例引用都是并行的,只有连续赋值语句(用关键词assign引用的语句)和实例引用语句可以独立于过程块而存在于模块的功能定义部分;在always模块内,逻辑是按照指定的顺序执行的,always块内的语句称为“顺序语句”,所以always块也称为“过程块”;

2 数据类型及其常量和变量

  Verilog HDL中总共有19钟数据类型:large型,medium型,scalared型,time型,small型,tri型,trio型,tril型,triand型,trior型,trireg型,vectored型,wand型,wor型,reg型,wire型,integer型,parameter型;

  常用的数据类型有:reg型、wire型、integer型、parameter型

2.1 常量

2.1.1 整数的表示形式

  整数的表示形式:

  <位宽>’<进制><数字>,这是一种全面的描述方式

  ‘<进制>数字>采用默认位宽,由机器系统决定,至少32位

  <数字>采用默认进制(十进制);

  各种进制的表示方法:

    二进制:b or B;

    八进制:o or O;

    十进制:d or D;

    十六进制:h or H;

  注:位宽指的是转换成二进制数以后的位数

2.1.2 x和z值

  x表示不定值,z代表高阻态。一个x可以用来定义十六进制数的4位二进制数的状态,八进制数的3位,二进制数的一位;z的表示方法同x类似,此外z还可写作“?”,在case表达式中建议这种写法,以提高程序的可读性。

2.1.3 负数

  一个数可以被定义成负数,只须在位宽表达式前加一个减号,减号必须写在数字定义表达式的最前面。

  下划线:下划线可以用来分割数的表达式以提高程序的可读性

2.1.4 符号常量

  用parameter来定义一个标识符代表一个常量,称为符号常量,说明格式如下:

        parameter 参数名1=表达式,参数名2=表达式,……参数名n=表达式;

  用localparam也定义一个标识符代表一个常量,称为符号常量,说明格式如下:

        localparam 参数名1=表达式,参数名2=表达式,……参数名n=表达式;

  localparam与parameter的含义其实都是一样的,区别在于parameter定义的常量可以在其他模块例化时改变其数值,而localparam定义的常量只能在模块内部定义时进行修改

2.2 变量

2.2.1 wire型(实际电路中的导线)

  网络型变量wire结构实体之间的物理连接,不能存储值,而且必须受到驱动器的驱动,没有驱动时该变量就是高阻的,即其值为z。wire型数据常用来表示以assign关键字指定的组合逻辑信号,Verilog程序模块中输出信号类型默认定义为wire型

  wire型信号的定义格式如下:

		wire [n-1:0]数据名1,数据名2,……数据名i;
		wire[n:1]数据名1,数据名2,……数据名i;
2.2.2 reg型

  寄存器是数据存储单元的抽象,寄存器数据类型的关键字是reg。reg数据类型的默认初始值为不定值x。reg型数据常用来表示“always”模块内的指定信号,常代表触发器,“always”模块内每一个被赋值的信号必须被定义成reg型

  reg型数据可以赋正值也可以赋负值,但当一个reg型数据是一个表达式中的操作数时,它的值被当做是无符号数,即正值。

2.2.3 memory型

   verilog HDL通过对reg型变量建立数组来对存储器建模,格式如下:

     reg [n-1:0] 存储器名 [m-1:0];

  reg[n-1:0]定义了每一个存储单元的大小,该存储单元是一个n位的寄存器,[m-1:0] 则定义了该存储器有多少个这样的存储单元。

  注:在同一个数据类型声明语句里,可以同时定义存储类型数据和reg型数据。

3 运算符及表达式

  Verilog HDL语言运算符按其功能可以分为以下几类:

    算数运算符(+,-,*,/,%);

    赋值运算符(=,<=);

    等式运算符(!=,==)

    关系运算符(<,>,=>,<=);

    逻辑运算符(&&,||,!);

    条件运算符(?:);

    位运算符(,|,^,&,^);

    拼接运算符({ })。

  除法运算结果略去小数,只取整数部分;取模运算(%,也称求余运算符)时,结果的符号位采用模运算式里的第一个操作数的符号位。

  注:在进行算术运算操作时,如果有一个操作数有不确定的值x,则整个结果也为不定值x。

  位运算:不同长度的数据进行位运算,系统自动按右端对齐,位数少的操作数会在相应的高位用0填满。

  注:算数运算符,关系运算符,逻辑运算符,条件运算符的规则都与C语言中相同。只是位运算符(除按位非运算符外,其余既可为单目也可为双目运算符)和拼接运算符与C语言有区别;

3.1 等式运算符

  (1)==(等于);

  (2)!=(不等于);

  (3)===(等于);

  (4)!==(不等于)。

  注意:求反号、双等号、三个等号之间不能有空格

  (1)和(2)又称逻辑等式运算符,当操作数中的某些位是不定值x或高阻值z时,结果为不定值x;而(3)和(4)对不定值和高阻值也进行比较,另两个操作数必须完全一样,结果才为1,(3)和“!==”常用与case表达式的判别,所以又称为“case等式表达式”。

3.2 缩减运算符(位运算符的单目形式)

  &是单目运算符,如:

	reg [3:0] B ;
    reg C;
    
    C = &B;

  相当于:
        C=((B[0]&B[1])&B[2])&B[3];

  具体运算过程是这样的:第一步先将操作数的第1位与第2位进与、或、非运算;第二步将运算结果与第3位进行与、或、非运算,直至最后一位。

3.3 拼接运算符

  {信号1的某几位,信号2的某几位,...,...,信号n的某几位},中间用逗号隔开,最后用大括号括起来表示一个信号整体。

  位拼接表达式中不允许出现没有指明位数的信号;

  位拼接可以用重复法来简化表达式:{4{w}}等同于{w,w,w,w};

  位拼接还可以用嵌套的方式来表达:{b,{3{a,b}}}等同于{b,a,b,a,b,a,b}。

二、赋值语句和结构说明语句

1 赋值语句

  为解释问题方便,下面定义两个缩写字:

  RHS——赋值等号右边的表达式或变量可分别缩写为RHS表达式或RHS变量;

  LHS——赋值等号左边的表达式或变量可分别缩写为LHS表达式或LHS变量;

1.1 非阻塞(Non_Blocking)赋值(“<=”)

  (1)为什么称这种赋值为非阻塞赋值呢?这是因为在赋值操作开始时计算非阻塞赋值符的RHS表达式,赋值操作结束时刻才更新LHS。在计算非阻塞赋值的RHS表达式和更新LHS期间,其它Verilog语句,包括其它的Verilog非阻塞赋值语句都能同时计算RHS表达式和更新LHS。非阻塞赋值允许其它的Verilog语句同时进行操作。

  (2)非阻塞赋值的操作过程可以看作两个步骤:

    1.在赋值开始时刻,计算非阻塞赋值RHS表达式;

    2.在赋值结束时刻,更新非阻塞赋值LHS表达式。

  (3)非阻塞赋值操作只能用于对寄存器类型变量进行赋值,因此只能用在“initial”块和“always”块等过程块中,而非阻塞赋值不允许用于连续赋值。

1.2 阻塞赋值(“=”)

  (1)为什么称这种赋值为阻塞赋值呢?这是因为在赋值时刻先计算等号右手方向(RHS)部分的值,这时赋值语句不允许任何别的Verilog语句的干扰,直到现行的赋值完成时刻,即把RHS赋值给LHS的时刻,它才允许别的赋值语句的执行。 一般可综合的阻塞赋值操作在RHS不能设定有延迟(即使是零延迟也不允许)。从理论上讲,他与后面的赋值语句只有概念上的先后,而无实质上的延迟。若在RHS上加延迟,则在延迟期间会阻止赋值语句的执行,延迟后才执行赋值,这种赋值语句是不可综合的,在需要综合的模块设计中不可使用这种风格的代码。

  (2)阻塞赋值的执行可以认为是只有一个步骤的操作,即计算RHS并更新LHS,此时不能允许有来自任何其它Verilog语句干扰。阻塞的概念:指在同一个always块中,其后面的赋值语句从概念上(即使不设定延迟)是在前一句赋值语句结束后再开始赋值的。

  (3)如果在一个过程块中阻塞赋值的RHS变量正好是另一个过程块中阻塞赋值的LSH变量,这两个过程块又用同一时钟沿触发,这时阻塞赋值操作会出现问题,即如果阻塞赋值的顺序安排不好,就会出现竞争。若这两个阻塞赋值操作用同一个时钟沿触发,则执行的顺序是无法确定的。

1.3 Verilog模块编程要点

  (1)时序电路建模时,用非阻塞赋值。

  (2)锁存器电路建模时,用非阻塞赋值。

  (3)用always块建立组合逻辑模型时,用阻塞赋值。

  (4)在同一个always块中建立时序和组合逻辑电路时,用非阻塞赋值。

  (5)在同一个always块中不要既用非阻塞赋值有用阻塞赋值。

  (6)不要在一个以上的always块中为同一个变量赋值。

  (7)用$strobe系统函数来显示用非阻塞赋值的变量值。

  (8)在赋值时不要使用 #0 延时。

  (9)如果always块中只有一条赋值语句,使用阻塞赋值或非阻塞赋值语句都可以。

2 块语句

2.1 顺序块(begin_end)与C语言的{}类似

  1.块内语句是按顺序执行的,即只有上面一条语句执行完后下面的语句才能执行

  2.每条语句的延迟时间是相对于前一条语句的仿真时间而言的。

  3.直到下一条语句执行完,程序流程控制才跳出该语句块。

        begin :块名 语句1;语句2;语句n; end

  begin 后可加块名,块内声明语句可以是参数声明语句,reg型变量声明语句,integer型声明语句和real型变量声明语句。

2.2 并行块(fork_join)

  1.块内语句是同时执行的,即程序流程一进入到该并行块,块内语句则开始同时并行地执行。

  2.块内每条语句的延迟时间是相对于程序流程控制进入到块内的仿真时间。

  3.延迟时间是用来给赋值语句提供执行时序的。

  4.当按时间顺序排序在最后的语句执行完后或一个disable语句执行时,程序流程控制跳出该程序块。

  格式如下:

	fork:块名

        语句1;
        
        语句2;
        
        语句n;
	join

  fork后可加块名,语句块内的说明语句可以是参数说明语句、reg型变量声明语句、integer型变量声明语句、real型变量声明语句、time型变量声明语句和事件(event)说明语句。如果两条语句在同一时刻对同一个变量产生影响,那么将会引起隐含的竞争。

  顺序块与并行块之间的根本区别在于:当控制转移到块语句的时刻,并行块中所有的语句同时开始执行,语句之间的先后顺序是无关紧要的。

2.3 块语句的特点

2.3.1 嵌套块

  块可以嵌套使用,顺序快和嵌套块能够混合在一起使用;

2.3.2 命名块(块可以具有名字)

  特点:
    (1)命名块中可以声明局部变量;

    (2)命名块是设计层次的一部分,命名块中声明的变量可以通过层次引用进行访问;

    (3)命名块可以禁用(用关键字disable);

2.3.3 命名块的禁用

  disable 可以用来从循环中退出,处理错误条件以及根据控制信号来控制某些代码段是否被执行,与C语言的break类似,但两者区别在于break只能退出当前所在循环,而使用disable则可以禁用设计中的任意一个命令块;

格式:disable 块名;

3 块名

  1、可以在块内定义局部变量,即只在块内使用的变量;

  2、可以通过块名被其他块调用,如disable语句。

  3、在Verilog语言中,所有的变量都是静态的,即所有的变量都只有一个唯一的存储地址,因此进入或跳出块并不影响存储在变量的值。

4 起始时间和结束时间

  对于顺序块,起始时间就是第一条语句开始被执行的时间,结束时间就是最后一条语句执行完的时间;对于并行快,起始时间对于块内所有的语句都是相同的,即程序流程控制进入该块的时间,其结束时间是按时间排序在最后的语句执行结束的时间。

  当把一个块嵌入到另一个块时,块的起始时间和结束时间是很重要的。至于跟在块后面的语句只有在该块的结束时间到了才开始执行。也就是说,只有该块完全执行完后,跟在后面的语句才可以执行。

三、条件语句,循环语句,块语句和生成语句

1 条件语句

1.1 if_else语句

  三种形式:

1、if(表达式)语句;

2、 if(表达式) 语句1;
     else 语句2;
3、if(表达式1)  语句1;
      else  if(表达式2)语句2;
      else  if(表达式3)语句3;
      ...
      else  if(表达式n)语句n;
      else   语句n+1;

  注:(1)条件语句必须在过程块中使用,所谓过程块是指initial和always语句所引导的执行语句集合。表达式为0或者x视为假。if语句支持嵌套。

  (2)if语句的语法与C语言中的语法基本一致;

1.2 case语句

  三种形式:

  (1) case(表达式) <case分支项> endcase

  (2) casex(表达式) <case分支项> endcase

  (3) casez(表达式) <case分支项> endcase

  分支项的一般格式如下:

   	分支表达式:        语句;
   	default:        语句;

注:
  1.default可有可无,但一般加上,防止生成锁存器以及死锁现象;

  2.每个分支项必须不同;

  3.所有表达式位宽必须相同,常犯错误:用’bx,’bz代替n’bx,n’bz;

  4.casez用来处理不考虑高阻值z的比较过程;

  5.casex用来处理将高阻值z和不定值x都视为不关心的过程;

  6.分支项可以为begin…end块;

  7.case语句支持嵌套;

  注意:为了避免Verilog代码综合后生成锁存器,如果用到if语句,最好写上else项,如果用到case语句最好写上default项。并且要使得这个else以及default是有效的,不然还是会生成锁存器;无效的组合逻辑else例如else a = a;

2 循环语句

2.1 forever

  forever语句:连续执行的语句;

        格式为: forever 语句;或 forever begin 多条语句 end

  注意:forever 循环语句常用于产生周期性的波形,只能用来作为仿真测试信号。他与 always 语句不同之处在于不能独立写在程序中,而必须写在 initial 块中;

2.2 repeat

  repeat语句:连续执行一条语句n次,只能用来作为仿真测试信号

        格式为: repeat(表达式)语句;或 repeat(表达式)begin 多条语句 end

2.3 while

  while语句:执行一条语句直到某个条件不满足;

        格式为: while(表达式)语句;或 while(表达式)begin 多条语句 end

2.4 for

  for语句:通过以下三个步骤决定语句的循环执行:

  1. 先给控制循环次数的变量赋初值。

  2. 判定控制循环的表达式的值,如为假,则跳出循环语句;如为真,则执行指定的语句后,转到第3步。

  3. 执行一条语句赋值语句来修正控制循环变量次数的变量值,然后返回第二步。

  格式为: for(表达式1;表达式2;表达式3)

  注意:在for语句中,循环变量增值表达式可以不必是一般的常规加法或者减法表达式,比如可以使用右移表达式。for循环执行方法与C语言一致,但是所表达的意思却有很大区别;

  Verilog HDL中的for循环语句一般是起复制电路的作用,综合软件在对代码进行综合时,会将for循环直接全部展开,每个变量综合出一个电路,程序下载到fpga中后,for循环综合得到的所有电路会同时运行,并不会像C语言那样去按顺序来执行循环变量。

  for循环的使用可以参考一篇文章,其实多数时候当我们需要对100路按键进行消抖的时候,可以写一个模块对一路按键消抖,在使用for循环将该模块例化100遍即可,当然也可以手动例化100遍,结果都一样,这100个模块上电后会同时运行,分别对100路按键输入进行消抖。

  Verilog HDL中的四种循环,只有for循环能够用于综合电路,其余三种只能用来写TestBench仿真激励文件,综合电路使用更多的还是如下循环结构。

 	generate
		for(表达式1 ; 表达式2 ; 表达式3)begin : 块名
			循环语句
		end
	endgenerate

四、结构语句、系统任务、函数语句和显示系统任务

1.1 initial语句

  initial语句格式:

	initial  begin
		语句1;
		语句2;
		……
		语句n;
	end

  注意:一个模块可以有多个initial块,它们都是并行运行的,initial常用与测试文件和虚拟模块的编写,用来产生仿真测试信号和设置信号记录等仿真环境;

1.2 always语句

  always语句格式:always <时序控制> <语句>

  如果一个always语句没有时序控制,则这个语句将会使仿真器产生死锁。always的时序控制可以是沿触发也可以是电平触发。沿触发的always块常常用来描述时序行为,通过综合工具转换为表示寄存器组合门级组合的组合逻辑结构;而电平触发的always块产长用来描述组合逻辑行为,通过综合工具转换为表示组合逻辑的门级逻辑和带锁存器的组合逻辑结构。

  always块的OR事件控制(敏感列表):

   由关键词“or”连接的多个事件名或者信号名组成的列表称为敏感列表,关键词“or”被用来表示这种关系,或者使用“,”来代替。此外,如果组合逻辑块的输入变量很多,Verilog提供另外两个特殊的符号:@*和@(*),它们都表示对其后面语句块中所有输入变量的变化是敏感列表;

  wait关键字表示的电平敏感时序控制,只能在TestBench中进行使用。

  Verilog同时也允许使用另外一种形式表示的电平敏感时序控制(即后面的语句和语句块需要等待某个条件为真才能执行);

  例:always
       wait (count_enable) #20 count=count+1;

  注意:一个程序模块可以有多个initial和always过程块,每个initial和always说明语句在仿真的一开始同时立即开始执行,initial语句只执行一次,而always语句则不断重复活动着,直到仿真过程结束;

1.3 task任务说明语句

1.3.1 任务的的定义 任务的定义语法如下:

	task <任务名>;
		 <端口及数据类型声明语句>
		<语句1>
		 <语句2>
		 ……
		 <语句n>
	endtask

1.3.2 任务的调用及变量的传递

     任务的调用: <任务名>(端口1,端口2,端口3,……端口n);

1.4 function函数说明语句

  函数的目的是返回一个用于表达式的值。

1.4.1 定义函数的语法:

	function    <返回值的类型或范围> (函数名);
		<端口说明语句>
		<变量类型说明语句>
			begin
				<语句>
				……
			end
	endfuction

  注:<返回值的类型或范围>这一项是可选项,如默认则返回值为一位寄存器类型数据。

1.4.2 从函数返回的值

  函数的定义蕴含声明了与函数同名的、函数内部的寄存器,函数的定义把函数返回值所赋值寄存器的名称初始化为与函数同名的内部变量。

1.4.3 函数的调用:

  函数的调用时通过将函数作为表达式中的操作数来实现的。

  调用格式如下:

        <函数名>(<表达式>,…<表达式>)

1.4.4 函数的使用规则:

  1.函数的定义不能包含任何的时间控制语句,即用任何用#、@或wait来标识的语句;

  2.函数不能启动任务,但可以调用其他函数

  3.定义函数时至少要有一个输入变量;

  4.在函数定义中必须有一条赋值语句给函数中的一个内部变量赋以函数的结果值,该内部变量具有和函数名相同的名字。

1.4.5 自动(递归)函数

  Verilog中的函数不能够进行递归调用的。设计模块中若某函数在两个不同的地方被同时并发调用,由于这两个调用同时对同一块地址空间进行操作,那么计算结果将是不确定的。

  若在函数声明时使用了关键字automatic,那么该函数将成为自动的或可递归的,即仿真器为每一次函数调用动态地分配新的地址空间,每一个函数调用对各自的地址空间进行操作。因此,自动函数中声明的局部变量不能通过层次名进行访问。而自动函数本身可以通过层次名进行调用。

  在定义时将automatic插入到function即可。

        格式 :function automatic <返回值的类型或范围> (函数名);

1.4.6 常量函数

  参数是常量的函数,这种函数能够用来引用复杂的值;

  例如:在工程中,参数化设计是非常常见的。模块接口的位宽,常见的有8位、16位、32位、64位和128位等;虽然功能相同,仅因为位宽不同,就要另外写一个模块,那设计工作就很繁复了。为此,我们可以采用参数化来实现,即用parameter来定义常数。但是参数化会遇到一个问题,就是某些信号的位宽跟此参数有着密切的关系。例如,我们可以使用parameter来定义FIFO的深度,但是表示FIFO深度的信号usedw,其位宽是跟参数相关的。如果深度为512,usedw位宽是9位,如果深度为1024,其位宽是10位。这时如果此模块可以自己计算位宽那就再好不过了。

  下面设计一个自动计算位宽的函数;

module ram(.. .. ..);
	parameter RAM_DEPTH = 256;
	input [clogb2(RAM_DEPTH)-1:0] addr_bus;
	..
	..
	//
	function integer clogb2(input integer depth)begin
		if(depth==0)
			clogb2 = 1;
		else if(depth!=0)
			for(clogb2==0;clogb2>0;clogb2=clogb2+1)
				depth = depth + 1;
	end
	endfunction
endmodule

1.5 关于使用任务和函数的小结

  1.任务和函数都是用来对设计中多处使用的公共代码进行定义;使用任务和函数可以将模块分割成许多个可独立管理的子单元,增加了模块的可读性和可维护性;它们和C语言中的子程序起相同作用。

  2.任务可以具有任意多个输入、输出和输入\输出(inout)变量;在任务中可以使用延迟、事件和时序控制结构,在任务中可以调用其它的任务和函数;

  3.可重入任务使用关键字automatic进行定义,它的每一次调用都对不同的地址空间进行操作。因此在被多次并发调用时,它仍然可以获得正确的结果;

  4.函数只能有一个返回值,并且至少要有一个输入变量;在函数中不能使用延迟、事件和时序控制结构,但可以调用其它函数,不能调用任务。

  5.当声明函数时,Verilog仿真器都会隐含的声明一个同名的寄存器变量,函数的返回值通过这个寄存器传递回调用处;

  6.递归函数使用关键词automatic进行定义,递归函数的每一次调用都拥有不同的地址空间,因此对这种函数的递归调用和并发调用可以得到正确的结果;

  7.任务和函数都包含在设计层次之中,可以通过层次名对它们进行调用;

1.6常用的系统任务

1.6.1 $display 和 $write任务

  1.作用:将参数p2到pn按参数p1给定的格式输出。

  2.格式:
         $display (p1,p2,……pn);
         $write(p1,p2,……pn);

  其中参数p1称为“格式控制”,参数p2到pn通常称为“输出列表”;

  3. $display自动地在输出后进行换行,$write则不是这样,如果想在一行里输出多个信息,可以使用$write。在$display和$write中,其输出格式控制是用双引号括起来的字符串,它包括以下两种信息:

  格式说明,由“%”格式字符组成;

输出格式说明
%h或%H以十六进制数的形式输出
%d或%D以十进制数的形式输出
%0或%O以八进制数的形式输出
%b或%B以二进制数的形式输出
%c或%C以ASCII码字符的形式输出
%v或%V输出网络型数据信号强度
%m或%M输出等级层次的名字
%s或%S以字符串的形式输出
%t或%T以当前的时间格式输出
%e或%E以指数的形式输出实型数
%f或%F以十进制数形式输出实型数
%g或%G以指数或十进制数的形式输出实型数,无论何种格式都以较短的结果输出.

  普通字符,即需要原样输出的字符。

换码序列功能
\n换行
\t横向跳格(即跳到下一个输出区)
\反斜杠字符\
‘’双引号字符"
\o1~3位八进制数代表的字符
%%百分符号%

注:
  1.可以通过在%和表示进制的字符中间插入一个0自动调整显示输出数据宽度的方式。

        例如:$displaay("d=%0h a=%0h",data,addr);

  2.在输出格式为十进制的情况下:若部分位为不确定位,则输出结果为大写的X,若输出部分位为高阻值,则输出结果为大写的Z;

        例如·:$display("%d",1'bx); 输出结果:x

  3.在输出格式为十六进制或八进制的情况下:若部分位为不定值,则该位进制数输出结果为大写的X;若部分位为高阻值,则输出结果为大写Z;

        例如:$display("%h",14'bx0_1010); 输出结果:xxXa

1.6.2 文件输出

  1.打开文件

        用法:$ fopen(“<文件名>”);
        用法:<文件句柄>=$ fopen(“<文件名>”);

  2.写文件

    系统任务 $fdisplay、$fmonitor、$fwire、$fstrobe都用于写文件;

        格式:$ fdisplay(<文件描述符>,p1,p2,……,pn);
        格式:$ fmonitor(<文件描述符>,p1,p2,……,pn);

  3.关闭文件

        用法:$fclose(<文件描述符>);

1.6.3 显示层次

  通过任何显示任务,比如$display、$write、$monitor或者$strobe任务中的%m选项的方式可以显示任何级别的层次。

1.6.4 选通显示

  $strobe与$display任务除了在执行顺序上有区别外,其余用法相同:$strobe总是在同时刻的其他赋值语句执行完成之后才执行的,而$display的执行顺序则是不确定的。$strobe的这种同步机制可以确保所有在同一时钟沿赋值的其他语句在执行完毕之后才显示数据。

1.6.5 值变转储文件

  值变存储文件(VCD)是一个ASCII文件,它包含仿真时间、范围与信号的定义以及仿真运行过程中信号值得变化信息。设计中的所有信号或者选定的信号集合在仿真过程中都可以被写入VCD文件。

  Verilog提供了系统任务来选择要转储的模块实例或者模块实例信号($dumpvars),选择VCD文件的名称($dumpfile),选择转储过程的起点和终点($dumpon,$dumpoff),选择生成检测点($dumpall)。具体使用见教材P97。

五、调试用系统任务和常用编译预处理语句

1 系统任务$monitor

  格式:

	$monitor(p1,p2,……pn);
	$monitor;
	$monitoron;
	$monitoroff;

  $monitor提供了监控和输出参数列表中的表达式或变量值的功能。当启动一个带有一个或多个参数的$monitor任务时,仿真器则建立一个处理机制,使得每当参数列表中变量或表达式的值发送变化时,整个参数列表中的变量或表达式的值都将输出显示。如果同一时刻,两个或多个参数的值发生变化时,则在该时刻输出只显示一次。

  但在$monitor中,参数可以是$time系统函数。这样参数列表中变量或者表达式的值同时发生变化的时刻可以通过标明同一时刻的多行输出来显示。例如:

        $ monitor($ time, ,"rxd=%b,txd=%b",rxd,txd);,其中的“,,”代表一个空参数,在输出时显示为空格。

  $monitoron和$monitoroff任务的作用是通过打开或关闭监控标志来控制监控任务$mmonitor的启动和停止,这样使得使得用户可以很容易的控制$monitor何时发生。

  其中$monitoroff任务用于关闭监控标志,停止监控任务$monitor。$monitoron则用于打开监控标志,启动监控任务$monitor。通常在通过调用$ monitoron来启动$ monitor时,不管$ monitor参数列表中的值是否发生变化,总是立刻输出显示当前时刻参数列表中的值,用于在监控的初始时刻设定初始比较值。

  在默认情况下,控制标志在仿真的起始时刻就已经打开了。在多模块调试的情况下,许多模块都调用了$monitor,因为任何时刻只能有一个$monitor起作用,因此需配合$monitoron和$monitoroff使用,把需要监视的模块用$monitoron打开,在监视完毕后及时用、$monitoroff关闭,以便把$monitor让给其它模块使用。

  $monitor与$display的不同处还在于$monitor往往在initial块中调用,只要不调用$monitoroff,$monitor便不间断地对所设定的信号进行监视。不需要、也不能在always过程块中调用$monitor。

2 时间度量系统函数

2.1 $time系统函数

  $time返回一个64位的整数来表示当前的仿真时刻值。该时刻是以模块的仿真时间尺度位基准的。$time输出的总是时间尺度的倍数,且总是输出整数(在将经过尺度比例变换的数字输出时,要先进行取整)。
  例如:

`timescale 10ns/1ns//时间尺度/时间间隔
module test;
	reg set;
	parameter p = 1.6;
	initial begin
		$ monitor($time,,"set=",set);
		#p set=0;
		#p set=1;
	end
endmodule

  输出结果·:

0 set=x
2 set=0 
3 set=1

2.2 $realtime系统函数

   $realtime和$time的作用是一样的,只是$realtime返回的数字是一个实型数,该数字也是以时间尺度位基准的。
  例如:

`timescale 10ns/1ns//时间尺度/时间间隔
module test;
 	reg set;
 	parameter p = 1.6;
 	initial begin
 		$ monitor($realtime,,"set=",set);
 		#p set=0;
 		#p set=1;
 	end
endmodule

  输出结果·:

0 set=x
1.6 set=0 
3.2 set=1

3 系统任务$finish

  格式:

		$finish;
		$finish(n);

  系统任务$ finish的作用是退出仿真器,返回主操作系统。如果不带参数,默认$finish的参数值为1。

  各种参数代表的含义:

  0 不输出任何信息;

  1 输出当前仿真时刻和位置;

  2 输出当前仿真时刻、位置和在仿真过程中所用的memory及cpu时间统计。

  需要特别注意的一点是,使用该系统任务后,程序运行到该任务会自动关闭仿真软件,有时候用户发现仿真软件运行后直接被关闭了,检查下TestBench文件中是否加入该任务吧,很可能是该任务造成的,因此更加推荐使用$stop任务。

4 系统停止任务$stop

  格式:

		$stop;
		$stop(n);

  $stop任务的作用是是把EDA工具(例如仿真器)置成暂停模式,在仿真环境下给出一个交互式的命令提示符,将控制权交给用户·。根据参数值的不同,输出不同的信息,参数越大,输出的信息越多。

5 系统任务$ readmemb和$readmemh

  $readmemb和$readmemh用来从文件中读数据到存储器中,这两个系统任务可以在仿真的任意时刻被执行使用,其格式共有以下六种:

    1.readmemb(“<数据文件名>,<存储器名>”);

    2.readmemb(“<数据文件名>,<存储器名>,<起始地址>”);

    3.readmemb(“<数据文件名>,<存储器名>,<起始地址>,<结束地址>”);

    4.readmemh(“<数据文件名>,<存储器名>”);

    5.readmemh(“<数据文件名>,<存储器名>,<起始地址>”);

    6.readmemh(“<数据文件名>,<存储器名>,<起始地址>,<结束地址>”);

  在这两个系统任务中,被读取的数据文件的内容只能包含:空白位置(空格、换行、制表(tab)和from-feeds),注释行(//形式的和//形式的都允许)、二进制和十六进制数字。

  数字中不能包含位宽说明和格式说明,对于$readmemb系统任务,每个数字必须是二进制数字,对于$readmemh系统任务,每个数字必须是十六进制数字。每个数字的存放地址值可以在数据文件中进行说明,@后跟上十六进制数。如:@hh…h

  补充说明:

  (1)如果系统任务声明语句和数据文件里都没有进行地址说明,则默认的存放起始地址为该存储器定义语句中的起始地址,数据文件里的数据被连续存放到该存储器中,直到该存储器单元存满为止或数据文件里的数据存完。

  (2)如果系统任务中说明了存放的起始地址,没有说明存放的结束地址,则数据从起始地址开始存放,存放到该存储器定义语句中的结束地址为止;

  (3)如果在系统任务声明语句中,起始地址和结束地址都进行了说明,则数据文件里的数据按起始地址开始存放到存储器单元中,直到该结束地址,而不考虑该存储器的定义语句中的起始地址和结束地址;

  (4)如果地址信息在系统任务和数据文件里都进行了说明,那么数据文件里的地址必须在系统任务中地址参数声明的范围之内。否则将提示错误信息,并且装载数据到存储器中的操作被中断。

  (5)如果数据文件里的数据个数和系统任务中起始地址及结束地址暗示的数据个数不同的话,也要提示错误信息。

6 系统任务$random

  这个系统函数提供了一个产生随机数的手段。当函数被调用时返回一个32位随机数。$random的一般用法是:$random%b,其中b>0。它给出了一个在 [(-b+1) :(b-1)] 中的随机数。

  例:

		reg[23:0] rand;
		rand=$ random%60;

  上面的例子给出了一个范围在-59到59之间的随机数,下面的例子通过位并接操作产生一个值在0~59之间的数。

  例:

		reg[23:0] rand;
		rand={$random}%60;

  利用这个系统函数可以产生随机脉冲序列或宽度随机的脉冲序列,以用于电路的测试。

7 编译预处理

  预处理命令以符号“`”开头,这些预处理命令的有效作用范围为定义命令之后到文本结束或其它命令定义替代该命令之处。

7.1 宏定义`define

  用一个指定的标识符(即名字)来代表一个字符串(宏内容),它的一般形式为:

					`define 标识符(宏名) 字符串(宏内容)

  例 : define signal string

  在软件综合时将宏名替换成字符串的过程称为“宏展开”,

  说明:

  (1)建议使用大写字母,以与变量名相区别;

  (2)`define命令可以出现在模块定义里面,也可以出现在模块定义外面,宏名有效范围为定义命令之后到原文件结束。通常,`define命令写在模块定义的外面,作为程序的一部分,在此程序内有效。

  (3)在引用已定义的宏名时,必须在宏名的前面加上符号“`”,表示该名字是一个经过宏定义的名字;

  (4)宏定义是用宏名代替一个字符串,也就是做简单的置换,不做语法检查。预处理时照样带入,不管含义是否正确,只有在编译已被展开后的源程序时才会报错。

  (5)宏定义不是Verilog HDL语句,不必在行末加分号,如果加了分号会连分号一起进行置换。

  (6)在进行宏定义时,可以引用已定义的宏名,可以层层置换。

  (7)宏名和宏内容必须在同一行中进行声明,如果在宏内容中包含有注释行,注释行不会作为被置换的内容。

  注意:

  组成宏内容的字符串不能被注释行、数字、字符串、确认符、关键词、双目和三目运算符分隔开的;

7.2 “文件包含”处理`include

  所谓“文件包含”处理是一个源文件可以将另一源文件的全部内容包含进来,即将另外的文件包含到本文件之中。其一般形式为:

												`include “文件名”

  说明:

  (1)一个`include命令之内指定一个被包含的文件,如果要包含n个文件,要用n个`include命令;注意下面的写法是错误的:

                   							`include "aaa.v""bbb.v";

  (2)`include命令可以出现在Verilog HDL源程序的任何地方,被包含文件名可以是相对路径名,也可以是绝对路径名。例如:

                   							`include "parts/count.v";

  (3)可以将多个`include命令写在一行,在`include命令行,可以出现空格和注释行。

  (4) 如果文件1包含文件2,而文件2要用到文件3的内容,则可以在文件1用两个`include命令分别包含文件2和文件3,而且文件3应出现在文件2之前。

  (5)在一个被包含文件中又可以包含另一个被包含文件,即文件包含是可以嵌套的。

7.3 时间尺度`timescale

  `timescale命令的格式如下:

                   							`timescale <时间单位>/<时间精度>

  时间单位参量是用来定义模块中仿真时间和延迟时间的基准单位,时间精度参量是用来声明该模块的仿真时间精确程度的。时间精度不能大于时间单位值。

  如果在同一个程序设计中,存在多个`timescale命令,则用最小的时间精度值来决定仿真的时间单位。

  用于说明时间单位和时间精度参量值的数字必须时整数;当多个带不同`timescale定义的模块包含在一起时只有最后一个起作用。

时间单位定义
s(秒)1s
ms(毫秒)千分之一秒(10-3s)
us(微秒)百万分之一秒(10-6s)
ns(纳秒)十亿分之一秒(10-9s)
ps(皮秒)万亿分之一秒(10-12s)
fs(飞秒)千万亿分之一秒(10-15s)

  注意:如果在同一个设计里,多个模块中用到的时间单位不同,需要用到以下的时间结构:

    (1)用`timescale命令来声明本模块中所用到的时间单位和时间精度;

    (2)用系统函数$printtimescale来输出显示一个模块的时间单位和时间精度;

    (3)用系统函数$time和$realtime及%t格式声明来输出显示EDA工具记录的时间。

7.4 条件编译命令`ifdef、`else、`endif

  条件编译:对一部分内容指定编译的条件

  条件编译命令有以下几种格式:

(1)

`ifdef(标识符)
	程序段1
 `else
	程序段2
 `endif

(2)

`ifdef(标识符)
	 程序段1
`endif

  条件编译命令可以用编译命令:`ifdef、`ifndef、`else、`endif实现,`ifdef语句中不允许使用布尔表达式(指的是不允许在(标识符)位置使用布尔表达式)。

7.5 条件执行

  条件执行标志允许设计者在允许时控制语句执行的流程,所有语句都被编译,但是有条件的执行它们,条件执行标志位仅能用于行为语句,系统任务关键字$test和$\plusargs用于条件执行;

  您的支持是我更新的最大动力!将持续更新工程,如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!

  • 107
    点赞
  • 757
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
Verilog是一种硬件描述语言(HDL),用于描述数字电路和系统。以下是Verilog常用语法元素: 1. 模块声明 Verilog程序由一个个模块(module)组成。模块声明包括模块名、输入和输出端口等。 ```verilog module module_name (input port1, input port2, output port3); // 模块内部代码 endmodule ``` 2. 数据类型 Verilog支持多种数据类型,包括位、字、整数、浮点数等。 ```verilog // 位类型 reg [7:0] data; // 8位寄存器 // 字类型 reg [15:0] addr; // 16位寄存器 // 整数类型 integer count = 0; // 浮点数类型 real value = 3.14; ``` 3. 运算符 Verilog支持多种运算符,包括算术运算符、位运算符、逻辑运算符等。 ```verilog // 算术运算符 a + b; // 加 a - b; // 减 a * b; // 乘 a / b; // 除 a % b; // 取余 // 位运算符 a & b; // 按位与 a | b; // 按位或 a ^ b; // 按位异或 ~a; // 按位取反 // 逻辑运算符 a && b; // 逻辑与 a || b; // 逻辑或 !a; // 逻辑取反 ``` 4. 控制语句 Verilog支持多种控制语句,包括条件语句、循环语句和跳转语句等。 ```verilog // 条件语句 if (condition) begin // 代码块 end else begin // 代码块 end // 循环语句 for (i = 0; i < 10; i = i + 1) begin // 代码块 end // 跳转语句 case (signal) 2'b00: // 代码块 2'b01: // 代码块 2'b10: // 代码块 2'b11: // 代码块 endcase ``` 5. 实例化模块 Verilog允许在一个模块中实例化另一个模块。 ```verilog module module1 (input port1, output port2); // 模块内部代码 endmodule module module2 (input port3, output port4); module1 m1 (.port1(port3), .port2(port4)); endmodule ``` 以上是Verilog常用语法元素,掌握这些基本语法是学习和设计FPGA电路的基础。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

电路_fpga

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值