当前位置:   article > 正文

Verilog HDL 语言基础_hdl语言

hdl语言

目录

前言

一、Verilog HDL模块基本结构

1.模块声明&端口定义&数据类型声明

                                                                                                       

二、数据类型

1、信号状态

2、整数

2.1、标准形式

2.2、补充

3、实数

4、字符串 

三、操作符

1、算数运算

2、逻辑运算

3、按位运算

4、关系操作符

5、等值操作符

6、缩减运算符

7、位移运算符

8、条件运算符

9、并接运算符

                                                                                                       

四、数据对象

1、常量

2、变量

2.1、连线型

2.2、寄存器型

2.3、数组

                                                                                                       

五、Verilog HDL 语言语句(语法)

0、时间 

1、过程语句

1.1、initial语句

1.2、always语句

1.3、task语句

1.4、function语句

2、块语句

2.1、Begin-end块的特点

2.2、Fork-join块的特点

3、赋值语句

3.1、连续赋值语句 assign

3.2、过程赋值语句

3.3、条件语句

3.4、循环语句

4、语句的顺序执行与并行执行

六、补充

6.1、随机数生成

6.2、时钟变频

6.3、时钟复位    

                                                                                                                                                           

前言

        Verilog HDL语言最初是在1983年,由Gateway Design Automation公司为其模拟器产品开发的硬件建模语言。由于该公司的模拟、仿真器软件应用比较广泛,因此Verilog HDL因为其实用性为越来越多的设计者所青睐。

        1990年,在一次努力增加语言普及性的活动中,Verilog HDL语言于被推向公众领域。

        Open Verilog International (OVI)是促进Verilog发展的国际性组织。1992年, OVI决定致力于推广Verilog OVI标准成为IEEE标准。

        Verilog 语言于1995年成为IEEE标准,称为IEEE Std 1364-1995。


一、Verilog HDL模块基本结构

        Verilog HDL 模块结构完全嵌在module和endmodule关键字之间。一个完整的Verilog HDL 设计模块包括:模块声明端口定义信号类型声明逻辑功能定义等部分。

  1. module FA_Seq (A, B, Cin, Sum, Cout); //模块声明
  2. input A, B, Cin; //输入端口定义
  3. output Sum, Cout; //输出端口定义
  4. reg Sum, Cout; //信号类型声明
  5. reg T1, T2, T3;
  6. always@ ( A or B or Cin ) //逻辑功能定义
  7. begin
  8. Sum = (A ^ B) ^ Cin;
  9. T1 = A & Cin;
  10. T2 = B & Cin;
  11. T3 = A & B;
  12. Cout = (T1| T2) | T3;
  13. end
  14. endmodule

1.模块声明&端口定义&数据类型声明

        模块声明部分包括模块名字以及模块所有输入/输出端口列表,格式为:

                module 模块名(端口1,端口2,…);

         Verilog HDL端口的类型有三种:

                input(输入);output(输出);inout(双向)。

        在模块名后的端口都应在此处说明其I/O类型。说明的格式为:

                端口类型 端口名;

        Verilog 所支持的数据类型有:

               连线型(wire);寄存器型(reg);整型(integer);实型(real);时间型(time)等等,系统默认的变量类型为1位wire类型。(后续将详细说明)

        信号类型声明格式为:

                数据类型 端口名;

        端口定义与数据类型声明的位置较为随意。

        注意向量数据的声明需要在其端口也加上位数([n-1:0)

  1. module module_name(port_namel,port_name2,...,port_name_n);
  2. port_typel port_name1;
  3. signal_type1 port_name1;
  4. port_type2 [length-1:0] port_name2;
  5. signal_typel [length-1:0] port_name2;
  6. ...
  7. port_type_n port_name_n;
  8. signal_type_n port_name_n;
  9. module module_name(
  10. port_type1 signal_typel port_name1;
  11. port_type2 signal_type2 [length-1:0] port_name2;
  12. )

                                                                                                       

二、数据类型

        Verilog数据类型主要有:整数型、实数型、字符串型等。任何数据在最后会转换为1、0、X、Z四种信号状态。

1、信号状态

        Verilog HDL拥有四种基本值:

  • 0:低电平、逻辑0或是假状态
  • 1:高电平、逻辑1或是真状态
  • X:不确定或是未知的逻辑状态
  • Z:高阻态

       注:X,Z大小写均可

2、整数

2.1、标准形式

        在Verilog HDL中,整数有四种进制:

  • b、B 二进制
  • o、O 八进制
  • d、D 十进制
  • h、H 十六进制

        表示形式为  <位数>'<进制><数值> ,位数为将数值转换为二进制数后的位数。

        其中二、八和十六进制数之间转换较为方便,八进制一位等效于二进制三位,十六进制一位等效于二进制四位。

  1. B = 12'b1011_1011_1011 = 12'b101_110_111_011;
  2. O = 4'o5_6_7_3;
  3. H = 3'hB_B_B;
  4. //B、O、H对于同一个十进制数

2.2、补充

        (一)当位宽大于实际数字的二进制宽度时,系统会自动对缺失的高位进行补位:

  • 若二进制数最高位为1或0,则缺失高位用0补全
  • 若二进制数最高位为X,则缺失高位用X补全
  • 若二进制数最高位为Z,则缺失高位用Z补全

        (二)位宽小于实际宽度时,数值溢出,多余的高位将被舍去。

        (三)省略位宽时,将默认为32位。

        (四)省略位宽以及进制时,将默认为32位十进制数。

                        255 = 32'd255

        (五)在数值中(非第一位之前)可以加入下划线 "_" 来方便观察。

  1. 8'b1_0010 = 8'b0001_0010
  2. 8'bx1_0010 = 8'bxxx1_0010
  3. 8'bz1_0010 = 8'bzzz1_0010
  4. 'b10010 = 32'b10010
  5. 18 = 32'b10010

        (六)负整数在语言编写中直接在数字前加上负号即可,但是在二进制代码中其通过补码形式来表现。

  1. 8'd18 = 8'b10010 = 8'b0001_0010
  2. -8'd18 = 8'b1110_1110

3、实数

        表示形式为:

  • 十进制表示:<十进制整数><小数点 "."><十进制整数>
  • 科学计数法表示:<任意数字><E或e><任意整数>

        其中,科学计数法的指数部分可以为正整数、负整数或是0

  1. 4.1E3 = 4700
  2. 5e-4 = 0.0005

4、字符串 

        字符串常量是由一对双引号括起来的字符序列,但不能分多行书写。

"HELLO WORLD"

        可用反斜线(\)来说明特殊字符

  1. \n //换行符
  2. \t //制表符
  3. \\ //字符"\"本身
  4. \" //字符"
  5. \206 //八进制数206对应的ASCII字符

                                                                                                                                                           

三、操作符

优先级操作符        操作符名称
1!  、 ~

逻辑非、按位取反

2

*、/、%

乘、除、求余(取模)

3

+、-

加、减
4

<<、>>

左移、右移

5

<、<=、>、>=

小于、小于等于、大于、大于等于

6

==、!=、===、!==

等于、不等于、全等、不全等

7

&、~&

缩减与,缩减与非

8

^、~^

缩减异或、缩减同或

9

|、~|

缩减或、缩减或非

10

&&

逻辑与
11||逻辑或
12

?:

条件运算

1、算数运算

        算术操作符有:

                +(加)、-(减)、*(乘)、 /(除)、%(求余/取模)

        其中除法只取整数部分。

  1. 8/5 = 1
  2. 8%5 = 3
  3. -8%5 = -3

        如果算术操作符中的任意操作数是x或z,那么整个结果为x

 A='b00x1;B='b1101;A+B='bxxxx   //结果为不确定数

2、逻辑运算

        逻辑操作符有:

                && (与)、|| (或)、!(非)

        对于位操作,与数字电子技术中的与、或、非操作完全一样。

        对于向量操作,0向量作为0处理,非0向量作为1处理。

  1. A ='b0000;B ='b0100;
  2. A || B = 1
  3. A && B = 0

3、按位运算

        按位运算操作符有:

                ~(按位取反)、&(按位与)、|(按位或)、^(按位异或)、~ ^或^ ~(按位同或)

        其中按位同或,~ ^与^ ~是等价的。

        按位运算为相同位之间的运算,位运算操作使原向量在对应位上按位操作,产生结果向量。若两向量长度不相等,长度较短的在最左侧添0补位

  1. A='b1101;
  2. B='b10000;
  3. //等价于
  4. A='b01101;
  5. B='b10000; 
  6. ~A = 'b10010;
  7. A & B = 'b00000;
  8. A | B = 'b11101;
  9. A ^ B = 'b11101;
  10. A ~^ B = 'b00010 ;

4、关系操作符

        关系操作符有:

                >(大于)、<(小于)、>=(大于等于)、<=(小于等于)

        关系操作符返回值为 真(1)或假(0)

        如果待比较数中有一位为x或z,那么结果为x。

  1. 63 < 23 = 0//假
  2. 63 > 4'b11x1 = X ; //结果为不确定

若两向量长度不相等,长度较短的在最左侧添0补位。

  1. A = 'b1001; B = 'b00110; 
  2. A = 'b01001; 
  3. B = 'b00110; 
  4. A >= B = 1; //结果为真

5、等值操作符

        等值关系操作符有:

                = =(等于)、!=(不等于)、= = =(全等)、!= =(不全等)。

        等值操作符返回值为真(1)或假(0)。在等于操作符使用中,如果待比较数中有一位为x或z,那么结果为x。在全等操作符使用中,值x和z严格按位比较,当成“确定值”进行运算。

  1. A='b0zx0;  B='b0zx0; 
  2. A == B = x; //结果不确定
  3. A ===B = 1//结果为真(全等)

        若两向量长度不相等,长度较短的在最左侧添0补位。

  1. A1 ='b1001;   B1 ='b01001; 
  2. A2 ='b01001;  B2 ='b01001; 
  3. A1 == B1 = 1
  4. A2 == B2 = 1

6、缩减运算符

        缩减操作符有:

                & (与)、~& (与非)、| (或)、~| (或非)、^ (异或)、~ ^ (同或)。

        缩减操作符在单一操作数的所有位上操作,并返回1位结果。其中部分算符与按位运算符相同,但是区别在于作用的操作数数量不同,按位运算为双操作数,缩减运算为单操作数。

        如果操作符中的任意操作数是x或z,那么整个结果为x。

  1. A='b0110;    |A = 1;
  2. B='b01x0;    ^B = x;

7、位移运算符

 

        移位操作符有:

                << (左移)、>> (右移)。

        移位操作是进行逻辑移位,移位操作符左侧是操作数,右侧是移位的位数。对于需要滚动的信号或变量,可以用位移运算代替赋值语句。

        移位所产生的空闲位由0来添补。如果操作符右侧的值为x或z,移位操作的结果为x。

  1. A = 'b0110;   
  2. A >> 2;
  3. A = 'b0001;
  4. A >> X;
  5. A = X; //结果未知

8、条件运算符

        条件操作符:

                ?:(条件操作符)

        条件操作符的使用格式:操作数=条件 ? 表达式1:表达式2;

        当条件为真(1)时,操作数=表达式1;

        当条件为假(0)时,操作数=表达式2。

y = a?b:c;    //当a=1时,y = b;当a=0时,y =c

9、并接运算符

        并接操作符:

                {}

        并接操作符的使用格式:

                {操作数1的某些位,操作数2的某些位,…操作数n的某些位};     

  1. {cout,sum}=ina+inb+inc; 
  2. //ina,inb,inc三数相加;数组中cout为高位,sum为低位

                                                                                                       

四、数据对象

        数据对象的作用是表示数字电路中的数据存储和传送单元,Verilog HDL语言的数据对象包括常量和变量。

1、常量

        在Verilog HDL中,用parameter语句来定义常量。其定义格式如下:

                parameter 常量名1=表达式,常量名2=表达式……;

  1. parameter a=5,b=8'hc0;
  2. //定义a为常数5(十进制),b为常数c0 (十六进制)

2、变量

2.1、连线型

        连线型指输出始终根据输入的变化而更新其值的变量,它一般指的是硬件电路中的各种物理连接。

        wire型变量是连线型变量中最常见的一种。wire型变量常用来表示assign语句赋值的组合逻辑信号。Verilog HDL模块中信号默认为wire型。用于模块间传输。

类型功能

wire,tri

连线类型(wire和tri功能完全相同)

wor,trior

具有线或特性的连线(两者功能一致)

wand,triand

具有线与特性的连线(两者功能一致)

tri1,tri0

分别为上拉电阻和下拉电阻

supply1,supply0

分别为电源(逻辑1)和地(逻辑0)

        wire型变量格式如下 :

  1. 1、定义宽度为1位的变量:
    1.                 wire 数据名1,数据名2,……数据名n;
  1. wire a,b; 
  2. //定义了两个宽度为1位的wire型变量a,b

2、定义宽度位n位的向量(vectors):

             wire[n-1:0]   数据名1,数据名2,……数据名n;

             wire[n:1]   数据名1,数据名2,……数据名n;

  1. wire[7:0] a;  wire[8:1] b;    
  2. //均定义一个8位wire型向量

3、若只使用其中某几位,可直接指明,注意宽度要一致。如:           

  1. wire[7:0] a;
  2. wire[3:0] b;
  3. assign a[6:3]=b; 
  4. //a向量的第6位到第3位与b向量相等

        “n位向量”即为一个长n位的变量,a[i]即为向量在第i各维度的投影(假设各维度基向量正交),也就是第i+1位。向量最右侧为0位,最左侧为最高位。 

2.2、寄存器型

        寄存器型变量对应的是具有状态保持作用的电路元件,如触发器、寄存器等。

        寄存器型变量与连线型变量的根本区别在于,寄存器型变量被赋值后,且在被重新赋值前一直保持原值,而连线型变量无法一直保持原值。用于模块内计算。

        寄存器型变量必须放在过程块语句(initial,always)中,通过过程赋值语句赋值。过程块内被赋值的每一个信号都必须定义成寄存器型。

        integer、real、time等3种寄存器型变量都是纯数学的抽象描述,不对应任何具体的硬件电路。reg型变量是最常用的一种寄存器型变量。

类型

功能说明

reg

常用的寄存器型变量

integer

32位带符号整数型变量

real

64位带符号整数型变量

time

无符号时间变量

 

        reg型寄存器变量格式如下:

                reg 数据名1,数据名2,……数据名n;

  1. reg ab;             
  2. //定义两个宽度为1位的reg型变量a,b

        向量reg型寄存器变量格式如下:

                reg[n-l:0]    数据名1,数据名2,……数据名n;

                reg[n:l]  数据名1,数据名2,……数据名n;

  1. reg[7:0] a;   reg[8:1] b;
  2. //均定义一个8位reg型向量

2.3、数组

        若干个相同宽度的向量构成数组,reg型数组变量即为memory型变量,即可定义存储器型数据。

reg[7:0] mymem[l023:0];

        上面的语句定义了一个1024个字节,每个字节宽度为8位的存储器。

        通常,存储器采用如下方式定义:

                parameter wordwidth=8,memsize = l024;

                reg[wordwidth-l:0] mymem[memsize-l:0];

        上面的语句定义了一个宽度为8位、1024个存储单元的存储器,该存储器的名字是mymem,若对该存储器中的某一单元赋值的话,采用如下方式:

  1. mymem[8]=1;          
  2. //mymem存储器中的第8个单元赋值为1

                                                                                                       

五、Verilog HDL 语言语句(语法)

        Verilog HDL的语句包括:

                过程语句;块语句;赋值语句;条件语句;循环语句

0、时间 

时间尺度设置:

                `timescale  1ns/1ps                `timescale  时间单位/时间精度

        其中“1ns”为时间单位,“1ps”为时间精度。 在Vivado文件的开头会自动设制1ns/1ps。

延时:

                #延时长度

        延长相应长度个时间单位的时间,即在此时间内什么都不执行。延时的时长以时间精度为单位增加:

  1. `timescale 1ns/1ps
  2. #5 //延时5纳秒,计时每次增加1皮秒
  3. `timescale 1ns/1ns
  4. #5 //延时5纳秒,计时每次增加1纳秒

时钟输入clk:

        clk一般是指外部晶振提供的方波时钟,占空比50%,即高低电平各占一个时钟周期的50%,1表示高电平,0表示低电平,高到低为下降沿,低到高为上升沿,一般都为上升沿有效时,触发寄存器(也可以下降沿触发,但是reg型必须在always块内进行编程)(一个周期内只存一次)。

        

1、过程语句

        过程语句一般有四种形式:

                initial语句;always语句;task语句;function语句

        一个程序可以有多条initial语句、always语句、task语句和function语句。

1.1、initial语句

        一个initial语句沿时间轴只执行一次,在执行完一次后,不再执行。若程序中有两条initial语句,则同时从开始并行执行。

        其格式为:

                initial

                begin

                        语句1;

                        语句2;

                end

  1. initial
  2. begin
  3. a ='b000000; //初始时刻a为0
  4. #10 a='b000001//10个时间单位a为1
  5. #10 a='b000010; //经20个时间单位a为2
  6. #10 a='b000011//30个时间单位a为3
  7. #10 a='b000100; //经40个时间单位a为4
  8. end
  9. // # 为延时算符

1.2、always语句

        always语句只要敏感信号被触发就可以一直重复执行语句中的敏感条件可以有多个并且用“or”连接。

        其格式为:

                always @ (敏感信号表)

                begin

                        语句1;

                        语句2;

                end

  1. always @(a or b or c ) //电平型敏感信号
  2. begin
  3. f = a & b & c;
  4. end
  5. //敏感信号为a、b、c三种信号的或运算,任一者电平变化时触发
  6. always @(posedge clk) --边沿型敏感信号
  7. begin
  8. counter = counter + 1
  9. end

        敏感信号分为两种:

                电平型和边沿型

        电平型通常是指高低电平的变化,而边沿型是指检测上升沿(posedge)、下降沿(negedge)。最好不要将这两种敏感信号用在一条always语句中。

        此外always语句还有其他延伸:

  • always@(*)        表示敏感信号根据语句内输入信号自动添加,无需人为操作。一般是电平触发(据说)。
  • always          没有敏感信号,执行完一次后立刻执行下一次,与forever类似
  1. always #20 CLK = ~CLK;
  2. //延时20时间单位
  3. //执行CLK取反,可以当成时钟来用,一个周期40个时间单位

        always@(*)为电平触发,always@(posedge) 为上升沿触发,而时钟输入clk一个周期内只有一次上升沿、一次下降沿以及一次高电平(1),因此理论上always@(*)与always@(posedge) 是等效的。

1.3、task语句

        task语句是包含多个语句的子程序,使用task语句可以简化程序结构,增加程序的可读性。task语句可以接收参数,但不向表达式返回值。

        格式如下:

                task 任务名;      

                端口声明;

                信号类型声明;

                        语句 1;

                        语句 2;

                endtask;

  1. task add4
  2. input[30] a,b;
  3. output[30] sum
  4. input ci;
  5. output co;
  6. {co,sum}= a+b+ci;
  7. endtask

1.4、function语句

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

        函数的定义格式为:

                function <返回值位宽或类型说明> 函数名(返回变量);

                端口声明;

                信号类型声明;

                        其他语句;

                endfunction

<返回值位宽或类型说明>这一项是可选项,如不特别声明默认返回值为一位寄存器类型数据。

  1. function[7:0] gefun;
  2. input[7:0] x;
  3. reg[7:0] count;
  4. integer i;
  5. begin
  6. count = 0;
  7. for(i=0;i<=7;i=i+1)
  8. if(x[i]=1’b0) count = count+1;
  9. gefun = count;
  10. end
  11. endfunction
  12. //函数名称即为返回变量的变量名
  13. //函数宽度或类型即为返回值的位宽或类型
  14. //此程序中gefun即为 函数名 以及 返回变量

2、块语句

        块语句为多个语句的组合,它们在格式上类似。

        有两种类型的块语句,一种是begin-end语句,通常用来表示顺序执行语句,用它来标识的块称为顺序块。

        另一种是fork-join语句,通常用来表示并行执行语句,块内的所有语句同时执行。用它来标识的块称为并行块。

  •        Begin-end        顺序执行        相当于c语言中的        { };
  •        Fork-join          并行执行

2.1、Begin-end块的特点

  1.         块内的语句是按顺序执行的,即只有上面一条语句执行完后,下面的语句才能执行; 
  2.         每条语句的延迟时间是相对于前一条语句的仿真时间而言的;
  3.         直到最后一条语句执行完,程序流程控制才跳出该语句块。

        顺序块的基本格式:

  1. begin
  2. sentence_1;
  3. sentence_2;
  4. ......
  5. end
  6. begin:块名
  7. 块内声明语句
  8. sentence_1;
  9. sentence_2;
  10. ......
  11. end

例:

  1. begin
  2. a = b;
  3. c = a;
  4. end
  5. //c的值为b

2.2、Fork-join块的特点

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

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

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

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

基本格式:

  1. fork
  2. sentence_1;
  3. sentence_2;
  4. ......
  5. join
  6. fork:块名
  7. 块内声明语句//声明变量
  8. sentence_1;
  9. sentence_2;
  10. ......
  11. join

例:

  1. fork
  2. wave = 0; //初值为0
  3. #50
  4. wave = 1; //50个时间单位后变为1
  5. #100
  6. wave = 0; //
  7. #200
  8. $finish; //200百个时间单位后结束
  9. join

3、赋值语句

  •         连续赋值语句 assign
  •         过程赋值语句 

3.1、连续赋值语句 assign

        连续赋值语句主要对wire型(线型)变量进行赋值。

        基本格式:

assign 变量 = 表达式 ;

        例:

assign c = a & b;

        a、b的任何变化都可以即时地被c反映出来,因此称之为连续赋值语句。及时反馈指的是语句出发条件为语句表达式中的元素发生改变,因此assign语句在程序中的位置较为自由。

3.2、过程赋值语句

        过程赋值语句用于对reg型(寄存器型)变量的赋值,有两种赋值方式:

  •         非阻塞(non_blocking)赋值
  •         阻塞(blocking)赋值

3.2.1、非阻塞赋值语句

        赋值符号为:

                <=

        比如" b <= a; "。非阻塞赋值的操作在语句所在块结束后进行,即b的值不会立刻改变,而是所在语句块结束后,下一语句块开始前改变。时钟沿触发。 

        例:

  1. always@(posegde clk) //时钟上升沿触发
  2. begin
  3. m = 2;
  4. n = 25;
  5. n <= m; //先不执行,n 仍然等于 25
  6. r = n; // r = 25
  7. end
  8. // 此时到 下一 时钟上升沿后 r = n 重新赋值 前 r 一直等于 2

3.2.2、阻塞语句

        赋值符号:

                =

        " b = a "。阻塞赋值在本语句结束后完成操作,即b的值在赋值语句处就已经改变了。在一个语句块中,若有多条阻塞赋值语句,则在前面赋值未完成时,后面的语句是不能被执行的,因此称为阻塞(blocking)赋值。

        例:

  1. always@(posegde clk)
  2. begin
  3. m = 2;
  4. n = 25;
  5. n = m; // n = 2
  6. r = n; // r = 2 从此刻到下一周期赋值前,r = 2
  7. end

        可见。非阻塞赋值的结果滞后于阻塞赋值。实际上阻塞赋值延时受限于输入信号最长延时路径,实际电路没有“同时”以及“瞬间”。

3.2.3、阻塞赋值语句实现非阻塞赋值

        通过使用"always"块,可使用阻塞赋值语句实现非阻塞赋值。

        例:

  1. always@(posedge clk)
  2. begin
  3. m = 2;
  4. n = 25;
  5. end
  6. always@(posedge clk)
  7. begin
  8. n = m;
  9. r = n;
  10. end

        实际上就是将非阻塞语句将赋值放在end后进行的特征用两个“always”块体现出来。但是两者并不完全相同,always为并行执行语句,因此赋值给r的值为上一个时钟周期结束时n的值。

        使用always语句时若敏感信号为posedge或negedge时可使用非阻塞赋值,其他情况下不建议使用非阻塞赋值。一般情况下assign及always均建议使用阻塞赋值。

3.3、条件语句

  •         case
  •         if

        两者都是顺序语句,应当应用于always语句中。

3.3.1、if语句

        条件判断语句,以顺序依此进行条件判断,若结果为真,则执行,为假,则跳过。

        if语句(if-else)主要有三种形式:

  1. a:
  2. if(表达式)
  3. 语句;
  4. b:
  5. if(表达式)
  6. 语句1
  7. else //否则
  8. 语句2
  9. c:
  10. if(表达式1
  11. 语句1
  12. else if(表达式2
  13. 语句2
  14. else if(表达式3
  15. 语句3
  16. ......
  17. else
  18. 语句n;

        语句可以是单句,也可以说一个顺序语句块(begin-end)

  1. if (a > b)
  2. begin
  3. sentence_1;
  4. sentence_2;
  5. end

        若条件表达式的结果为x或z(不确定或高阻态),按照逻辑假处理(不执行)。

        if-else语句叠加不宜过多,实际上一个if-else语句对应一个选择器,而不同if-else语句间为顺序关系(各选择器串联),级数越多整个语句组的延时就越长,容易影响实际电路的时序。

3.3.2、case语句

        if语句对于一个表达式只有两种选择,case语句对于一个表达式可以有多重分支(选择)。

        case语句主要有三种形式:

  1.         case(敏感信号)        <case分支项1~n>        endcase
  2.         casez(敏感信号)        <case分支项1~n>        endcase
  3.         casex(敏感信号)        <case分支项1~n>        endcase

         敏感信号输入信号。在case语句中,敏感信号与判定值1~值n之间的比较为全等比较,每一位必须相等;在casez语句中,如果敏感信号某一位为高阻态z,则高阻态不需要与分支判定值进行比较,只需要关注其他位的比较结果;在casex语句中,若双方某一位为x或z,均视为不确定值不参加比较,只需比价其他位即可

        此外,敏感信号中的x或是z可以用"?"(无关值表示符号)来表示。

        分支项表示方式:

                判定值:语句(若需要可使用begin-end块)

        例:

  1. case(a)
  2. 2'b1x : out = 1; //仅当 a = 1x 时 out = 1
  3. endcase
  4. casez(a)
  5. 2'b1x : out = 1; //当 a = 1x 1z 时 out = 1
  6. //此时1z由于为高阻态,因此只比较首位的 1
  7. endcase
  8. casex(a)
  9. 2'b1x : out = 1; //当 a = 10 11 1x 1z xx zz x1 z1 x0 z0 时 out = 1
  10. //此时 双方至少有一位的后位为x,因此最多只比较前一位
  11. endcase

        虽然if-else叠加也可以实现case语句的功能,但是case语句实际上相当于多输入选择器,延时远小于if-else的叠加。 

3.4、循环语句

        Verilog HDL有四种循环语句来控制执行次数。

  1. for
  2. repeat
  3. while
  4. forever

        循环语句不需要结束标识,仅执行语句后的一条语句或是后一块(begin-end或fork-join) 。

3.4.1、for语句 

        for循环语句与C语言的for循环语句非常相似,只是Verilog HDL语言需要用n=n+1的形式来表示循环变量变化,同时for语句的控制语句使用";"来间隔。格式为:

                for( 循环变量初值 ; 循环结束条件 ; 循环变量变化条件 )   

        例:

  1. for( n = 0 ; n < 8 ; n = n + 1)
  2. out = out ^ a[n] ; //八位奇偶校验

3.4.2、repeat语句

        repeat语句执行指定循环数,如果循环计数表达式的值不确定,即为x或z时,那么循环次数按0处理,格式为:

  1. repeat(循环次数表达式)
  2. begin
  3. 语句;
  4. ......
  5. end

        例:

  1. repeat(size)
  2. begin
  3. a = b <<1;
  4. end
  5. //左移size位

3.4.3、while语句 

        while语句执行时每次循环都会判断循环执行表达式是否为真,若为真则执行循环体中语句,若为假则跳出循环,不再执行判断。x及z也当做逻辑假处理

        在逻辑运算符中提到:0向量(即'b00~0)当做逻辑0处理,非零向量当做逻辑1处理,但是在while的循环执行表达式中非零向量或是一个将被当做执行次数处理。

       基本形式:

  1. while(循环执行表达式)
  2. begin
  3. 语句;
  4. ......
  5. end

        例:

  1. while(temp)
  2. begin
  3. count = count + 1;
  4. end
  5. //count 加 temp 次 1

 3.4.4、forever语句

        forever语句永久执行过程语句。中止语句与过程语句共同使用可跳出循环。在过程语句中必须使用某种形式的时序控制,否则forever循环将永久循环下去。forever语句必须写在initial模块中,用于产生周期性波形(激励文件中使用),格式为:

  1. forever
  2. begin
  3. 语句;
  4. ......
  5. end

        例:

  1. forever
  2. begin
  3. if(d)
  4. a = b + c;
  5. else
  6. a = 0;
  7. end
  8. //当 d = 1 时 a = b + c
  9. //本循环无法结束

4、语句的顺序执行与并行执行

        编写Verilog HDL程序时,首先要弄清楚哪些操作是并行执行的,哪些操作是顺序执行的。在always模块内,语句按照书写顺序顺序执行。always模块之间是并行执行的。两个或更多个always模块、assign语句、实例元件等都是并行执行的。

        下面的例子说明always模块内的语句是顺序执行的:

  1. module ex1(q,a,clk)
  2. output q,a;
  3. input clk;
  4. reg q,a;
  5. always@(posedge clk)
  6. begin
  7. q = ~q; //q 取反
  8. a = ~q; //将 q(实际值为q反) 取反 赋值给 a
  9. end
  10. endmodule //a,q始终结果逻辑相反
  11. module ex2(q,a,clk)
  12. output q,a;
  13. input clk;
  14. reg q,a;
  15. always@(posedge clk)
  16. begin
  17. a = ~q; //q取反 赋值给 a
  18. q = ~q; //q取反
  19. end
  20. endmodule //a,q相同

        如果将上述两句赋值语句分别放在两个"always"模块中,这两个"always" 模块放置的顺序对结果并没有影响,因为这两个模块是并行执行的

  1. module ex1(q,a,clk)
  2. output q,a;
  3. input clk;
  4. reg q,a;
  5. always@(posedge clk)
  6. begin
  7. q = ~q; //q 取反
  8. end
  9. always@(posedge clk)
  10. begin
  11. a = ~q; //将 q(实际值为q) 取反 赋值给 a
  12. end
  13. endmodule //a,q相同
  14. module ex2(q,a,clk)
  15. output q,a;
  16. input clk;
  17. reg q,a;
  18. always@(posedge clk)
  19. begin
  20. a = ~q; //q取反 赋值给 a
  21. end
  22. always@(posedge clk)
  23. begin
  24. q = ~q; //q取反
  25. end
  26. endmodule //a,q相同

        结果a,q均相同。

                                                                                                                                                          

六、补充

6.1、随机数生成

        主要使用random函数以及%取模运算符。

  1. a = {$random}%1024
  2. #a为范围是0~1023的随机数

6.2、时钟变频

        FPGA开发板上面只有一个晶振,即只有一种频率的时钟,如果需要用到不同频率的时钟,就需要在这个固定的时钟频率条件下进行分频或者倍频;

  •         分频:降低时钟频率;
  •         倍频:提高时钟频率。

6.2.1偶分频

        偶分频较为简单,首先确定分频系数M和计数器值N,其中:

  • M = 晶振(输入)频率 / 时钟输出频率
  • N = M / 2

        当晶振的时钟频率为50MHz,需要输出时钟频率为1Hz时,M = 'd50_000_000, N = 'd25_000_000。偶分频代表着M是偶数。

        因此只需要一个计数器 counter 由时钟驱动累加,直到 counter = (N-1) 时 clk_out翻转即可。

        偶分频分频模块:

  1. module clock_div(
  2. input clk,
  3. input rst,
  4. output reg clk_out
  5. );
  6. parameter N = 26'd25_000_000 , WIDTH = 25;
  7. reg [WIDTH:0] counter;
  8. always @(posedge clk or negedge rst)
  9. //电平翻转两次为一个周期,因此上升沿下降沿都需要被记录
  10. begin
  11. if (~rst) begin
  12. counter <= 0;
  13. clk_out <= 0;
  14. end
  15. else if (counter == N-1) begin
  16. clk_out <= ~clk_out;
  17. counter <= 0;
  18. end
  19. else
  20. counter <= counter + 1;
  21. end
  22. endmodule

6.3、时钟复位    

        Verilog HDL中,clk代表时钟信号,rst代表复位信号

        时钟信号clk在数字电路中非常重要,它提供了一个系统级的定时信号,用于同步各个模块之间的操作。在设计数字系统时,我们通常会以时钟的上升沿(或下降沿)为触发条件来完成一些操作,如寄存器的更新、状态机的跳转等。

        复位信号rst用于系统的初始化,通常在上电或者系统复位时使用。rst信号为高电平时,系统中的各个模块会被强制进入一个特定的状态,以确保系统的正确性。在复位信号消失后,系统会进入正常的工作状态。

        在Verilog HDL中,我们可以使用always关键字和posedge(上升沿触发)或negedge(下降沿触发)关键字来描述时钟信号的边沿触发行为。同时,我们也可以使用:

always @(posedge clk or posedge rst)

这样的语法来同时监听时钟信号和复位信号,以实现系统的复位功能。

        rst声明端口:

  1. module example(
  2. input rst,//reset信号
  3. );

        定义rst默认值:

  1. reg rst = 1'b0;
  2. //默认值为0

        可以在条件判断语句中自主设定复位为高电平有效还是低电平有效,一般情况下若使用按钮作为rst信号源,则一般为高电平触发复位,具体情况视按钮类型而定。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/煮酒与君饮/article/detail/816284
推荐阅读
相关标签
  

闽ICP备14008679号