当前位置:   article > 正文

FPGA数字电子技术复习笔记(一)verilog语法规则补充(语法篇2)(及ASIIC表)_verilog中什么时候可以省略定义

verilog中什么时候可以省略定义


通过学习数字电子技术(康华光 华中科技大学)总结
大概是第1、2、4节有关Verilog的部分


语法篇系列目录

FPGA学习笔记(二)Verilog语法初步学习( 语法篇1)

FPGA数字电子技术复习笔记(一)verilog语法规则补充(语法篇2)

FPGA学习笔记(七)verilog的深入学习之任务与函数(语法篇3)


重要声明

作为HDL语言,有两种基本的用途:系统仿真和设计实现。所有的HDL描述都可用于仿真,但并非所有的HDL描述都可综合。

参考:FPGA学习笔记—Verilog HDL 可综合语句和不可综合语句汇总


数字信号实际波形

在这里插入图片描述

Verilog语法规则(在之前的基础上补充)

衔接之前的:FPGA学习笔记(二)Verilog初步入门

间隔符

不在字符串中的空格(\b)、TAB(\t)、换行符(\n)、换页符被忽略,所以写程序时可以跨越多行书写。

标识符

起名字可以用英文字母、下划线(这两个能开头)、数字、$组成的标识符

常量表示规则

+/- 位宽 基数符号 数值
分别对应正负、常量对应二进制数的宽度、定义后面数值的表示形式、数值左边为最高位,右边最低位。
+号一般省略
例子:3‘d2
代表数值范围:0~2的三次方即 0 ~ 8。此时数值为2(十进制)。

字符串

字符串是“ ”中间的字符序列,不允许多行书写,在表达式和赋值语句中,要转换成无符号整数,用8位的ASCII表示
例如:“ab”等价为 16’h5758,一个字符是8位,所以n个字符就定义为n*8位

这里使用串口助手要注意:
在这里插入图片描述
在这里插入图片描述

参考:ASCII码对照表

寄存器类型

除了我们常见的reg类型,还有其他三种

  • integer:32位带符号的整数型变量
  • real:64位带符号的实数型变量,默认值为0
  • time:64位无符号的时间型变量
    这三种是纯数学的表述,不会生成电路。reg一般是无符号的数来处理的。
    例子:
integer counter;
initial
       counter=-1
  • 1
  • 2
  • 3

类似c语言,如果把实数类型(real)传递给integer时,只能保留整部部分,实数的小数部分会被舍弃。

  • 实数型常量的表达方式也可以用科学计数法表示
    例:
    23.51e2–>2351.0
    3.6E-1 —> 0.36

  • time型变量通常用来存储仿真时间
    例:

time current_time;
initial 
   current_time=$time;  //利用系统函数获取当前仿真时间
  • 1
  • 2
  • 3

缩位运算符

缩位运算符是单目运算符有:&、~&、|、 ~|、^、 ~ ^或者 ^ ~
分别是与、与非、或、或非、异或、同或
主要计算过程:第一步先将操作数的第一位与第二位进行或与非运算,第二步将运算结果与第三位进行或与非运算,依次类推,直至最后一位。
例:
A=4b‘1010
~^A=1
过程:(~1)^0=0,
(~0)^1=0,
(~0)^0=1,

门级原件

在这里插入图片描述
调用方法:

  • 多输入门:
    在这里插入图片描述

A1是名字,可以省略。括号第一个参数必须是输出变量。
例子:
在这里插入图片描述

  • 多输出门:
    在这里插入图片描述
    允许有多个输出,但只有一个输入,还是那个名字可以省略。
    例子:在这里插入图片描述
  • 三态门:
    有一个输出、一个数据输入和一个输入控制。如果输入控制信号无效,则三态门的输出为高阻态z。 (l类似之前verilog入门文章里面的inout),调用名还可以省略。

例子:bufif1 B1(out,in,ctrl);
在这里插入图片描述

逻辑功能描述

!!!!!
主要是三种:利用基础门级电路、assign(连续赋值语句,也叫数据流描述方法)、initial/always(过程块语句结构,也叫行为描述方法)

例如:
在这里插入图片描述
第一种:

module mux2to1(D0, D1, S, Y );
  input D0, D1, S;  //定义输入信号
  output Y;    //定义输出信号
  wire Snot, A, B ; //定义内部节点信号数据类型
//下面对电路的逻辑功能进行描述
  not U1(Snot, Sl); 
  and U2(A, D0, Snot);
  and U3(B, D1, S);
  or U4(Y, A, B);
endmodule                      
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

第二种:assign只能这种数据表达形式,不想always中,有语句可以用

module mux2to1_dataflow(D0, D1, S, Y );
  input D0, D1, S;  
  output Y;
  wire Y ; 
//下面是逻辑功能描述
  assign Y = (~S & D0) | (S & D1); //表达式左边Y必须是wire型
endmodule 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

第三种:相当与把电路化出了最简单的状态,更高效了

module mux2to1_bh(D0, D1, S, Y );
  input D0, D1, S;  
  output Y;
  reg Y ; 
//逻辑功能描述
   always @(S or D0 or D1) //任何输入信号的变化都会进入,括号里面的也叫敏感变量,也可以用*代替
           if (S == 1)  Y = D1;  //也可以写成 if (S)  Y = D1;
           else  Y = D0;   //注意表达式左边的Y必须是reg型
endmodule          
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

不过现在都用别的方法定义模块了
例子:

module top_seg_led
(
    //global clock
    input            sys_clk  ,       // 全局时钟信号
    input            sys_rst_n,       // 复位信号(低有效)
    output           led,        //1个LED灯
    //seg_led interface
    output    [5:0]  seg_sel  ,       // 数码管位选信号
    output    [7:0]  seg_led ,         // 数码管段选信号
    input              key         //按键信号
);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

这里面的有位宽定义的一般不能去掉前面的output/input。如下:

module top_seg_led
(
    //global clock
    input            sys_clk  ,       // 全局时钟信号
                     sys_rst_n,       // 复位信号(低有效)
                     key         //按键信号
    output           led,        //1个LED灯
    //seg_led interface
    output    [5:0]  seg_sel  ,       // 数码管位选信号
    output    [7:0]  seg_led ,         // 数码管段选信号
   
);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2022.10.18更新

嵌套case语句语法及casez/casex

嵌套case语句:

case(A)
     1'b1:case(B)
     	       2'b0:begin 
     	           		x1=1'b1;
		     	        x2=1'b0;
     	            end
     	       2'b01:begin 
     	           		x3=1'b1;
		     	        x4=1'b0;
     	            end     	            
     	        default:.........
     	   endcase
     default:.....
endcase
     	                   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

一个case语句不需要加begin end,像1‘b1里的。但是在具体的分支里面,多个语句还是要有begin end

casex/casez:
casez将比较双方表达式(分支表达式和case_expr控制表达式)中出现的z的位当作不用关心的位处理,在分支表达式中所有为z的位也可以用?代替。
casex则认为表达式中所有的z和x都是无关项

casex(I)
	8'b1xxx_xxxx:  //只要I的最高位i等于1就执行,相当if(I[7])
			y=3'd7;
	8'b01xx_xxxx: 
			y=3'd6;
	8'b001x_xxxx: 
			y=3'd5;
	8'b0001_xxxx: 
			y=3'd4;
	8'b0000_1xxx: 
			y=3'd3;		
			...............
	8'b0000_0001:
		    y=3'd0;
	default:.......	    						
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

2022.10.22更新

数组

在 Verilog 中允许声明reg,integer,time,real,realtime及其向量类型的数组,对数组的维数没有限制。线网数组也可用于连接实例的端口,数组中的每个元素都可以作为一个标量或向量。

integer count [0: 7] ; //由8个计数变量组成的数组
reg bool[31:0] ;   // 由32个1位的布尔( boolean)寄存器变量组成的数组
reg [4:0] port_id[0:7]; //由8个端口标识变量组成的数组,端口变量的位宽为5
integer matrix [4:0][0:255] ; //二维的整数型数组
reg [63:0] array_4d [15:0][7:0][7:0][255:0] ;//四维64位寄存器型数组
wire [7:0] w_array2 [5:0] ;//声明8位向量的数组
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

下面有一些赋值例子:

matrix[1] [0] = 33559;//把数组中第1行第0列的整数型单元(32位)置为33559
array_4d[0] [0][0][0][15:0] = 0;//把四维数组中索引号为[0][0][0][0]的寄存器型单元的0~15位都置为0
matrix = 0 ; //非法,企图写整个数组
matrix [1] = 0;//非法,企图写数组的整个第2行,即从matrix[1] [0]直到matrix[1][255]

reg [17:0] ADD_SUM_DI[0:251];//初始化
ADD_SUM_DI[0]=18'd1;  //赋值当然只能在always进行
ADD_SUM_DI[1]=18'd2; 
ADD_SUM_DI[2]=18'd3; 
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

字符串中的转义字符

就是字符串中的%要写成%%才行
在这里插入图片描述
在这里插入图片描述


2022.10.23更新

循环语句

while循环

在while语句中可以使用各种操作符,并且可以用这些操作符构成任意的逻辑表达式。如果循环中有多条语句,则须使用begin和end。

while((i < 16) && continue ) //用操作符的多个条件构成while表达式
begin
   if (flag[i])
   begin
       $display ("Encountered a TRUE bit at element number %d",i);
       continue = 'FALSE;
   end
  i = i + 1;
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

for循环

初始条件和完成自加操作的过程赋值语句都包括在 for循环中。

for ( count=0; count < 128; count = count + 1)
  • 1

repeat循环

repeat循环的功能是执行固定次数的循环,它不能根据一个逻辑表达式来确定循环是否继续进行。repeat循环的次数必须是一个常量、一个变量或者一个信号。如果循环重复次数是变量或者信号,循环次数是循环开始执行时变量或者信号的值,而不是循环执行期间的值。

//说明:从О增加到127计数并显示integer count;
initial
begin
   count = 0 ;
   repeat (128)
    begin
        $display ( "Count = %d",count) ;
        count = count + 1 ;
     end
end

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

forever循环

关键字forever用来表示永久循环。直到遇到系统任务$finish为止。如果需要从forever循环中退出,可以使用disable 语句。
通常情况下forever循环是和时序控制结构结合使用的。如果没有时序控制结构,那么仿真器将无限次地执行这条语句 ,并且仿真时间不再向前推进,使得其余部分的代码无法执行。

//例1:时钟发生器
//用forever循环,不用always块
reg clock;
initial
  begin
		clock = 1'b0 ;
		forever #10 clock = ~clock; //时钟周期为20个单位时间
  end
//例2:在每个时钟正跳变沿处使两个寄存器的值一致reg clock;
reg x, y;
initial
       forever @(posedge clock) x = y;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

块语句

顺序块

即begin end语句
一条一条语句顺序执行的,这里指的是阻塞赋值的情况下
在写仿真的时候,如果语句包括了延时控制,那么延时下一条语句要在前面这条延时处理完才能执行

//说明2:带延迟的顺序块reg x, y;
reg [1:0] Z, w;
initial
begin
 x= l'b0; //在仿真时刻o完成
 #5 y = l'b1;//在仿真时刻5完成
 #10 z = {x,y}; //在仿真时刻15完成
 #20 w = {y,x}; //在仿真时刻35完成
end

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

并行块

关键字fork和join声明,语句是并行执行的,但是要注意如果两条语句在同一时刻对同一变量进行操作,可能引起隐含的竞争,因为目前的仿真器无法正确的处理竞争,所以要避免这一写法。
例子:应用在仿真里面

//例1:带延迟的并行块reg x,y;
reg [1:0] z, w;
initial
fork
   x= 1 'b0; //在仿真时刻О完成
   #5 y = 1'b1; //在仿真时刻5完成
   #10 z = {x, y}; //在仿真时刻10完成
   #20 w = {y, x}; //在仿真时刻20完成
join

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

顺序块和并行块可以互相嵌套

命名块

和上面看两个并不是并列关系,但是同等重要。
命名块主要指给前面两种块起个名字,而且命名后的块里面的变量可以通过名字引用访问,也可以被禁用,停止执行。

//命名块
module top;

initial
begin: block1 //名字为block1的顺序命名块
integer i; //整型变量i是block1命名块的静态本地变量
//可以通过层次名top.block1.i被其他模块访问
…
end

initial
fork : block2 //名字为block2的并行命名块
reg i; //寄存器变量i是block2命名块的静态本地变量
//可以通过层次名top.block2.i被其他模块访问
...
join

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

具体语法就是在之前的额块语句begin/fork后面加一个:,之后后面再跟上起的名字

Verilog通过关键字disable提供了一种终止命名块执行的方法。使用disable则可以禁用设计中的任意一个命名块。

//在(向量)标志寄存器的各个位中从低有效位开始查找第一个值为1的位1/从向量标志寄存器的低有效位开始查找第一个值为1的位
reg [15:0] flag;
integer i; //用于计数的整数

initial
begin
  flag = 16'b 0010_0000_0000_0000;
  i = 0;
  begin: block1 // while循环声明中的主模块是命名块block1
  while(i < 16)
    begin
     if(flag[i])
	     begin
	     $display( "Encountered a TRUE bit at element number %d",i) ;
	     disable block1; //在标志寄存器中找到了值为真的位,禁用block1end
	     i = i + 1;end
	     end
    end
end

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

生成块

当对向量中的多个位进行重复操作时,或者当进行多个模块的实例引用的重复操作时,或者在根据参数的定义来确定程序中是否应该包括某段Verilog 代码的时候使用生成块

循环生成语句

例如:对两个N位总线变量进行按位异或

//本模块生成两条N位总线变量的按位异或
module bitwise_xor (out, i0,i1);//参数声明语句。参数可以重新定义
parameter N = 32; //默认的总线位宽为32位
//端口声明语句
output [N-1:o] out;
input [N-1:0]i0,il;
//声明一个临时循环变量,该变量只用于生成块的循环计算。verilog仿真时该变量在设计中并不存在
genvar j;
//用一个单循环生成按位异或的异或门( xor)
generate for (j=0; j<N; j=j+1)begin: xor_loop
xor gl (out [j], i0[j], i1[j]);
end //在生成块内部结束循环
endgenerate //结束生成块

//另外一种编写形式
//异或门可以用always块来替代
 // reg [N-1:0] out;
//generate for (j=0; j<N; j=j+1) begin: bit
//always (i0[j] or i1[j]) out [j] = i0[j] ^ i1[j];
// end
//endgenerate
endmodule

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

在仿真开始之前,仿真器会对生成块中的代码进行确立(展平),将生成块转换为展开的代码,然后对展开的代码进行仿真。
关键词genvar用于声明生成变量,生成变量只能用在生成块中;在确立后的仿真代码中,生成变量是不存在的;
生成变量的值只能由循环生成语句来改变;
循环生成语句可以嵌套使用。但是使用同一个生成变量作为索引的循环生成语句不能相互嵌套;
xor_loop是赋予循环生成语句的名字,目的在于通过它对循环生成语句中的变量进行层次化引用。因此,循环生成语句中各个异或门的相对层次名分别为: xor_loop[0].g1,xor_loop[1].g1,…,xor_loop[31].g1。

条件生成语句

利用if else来选择执行的模块

//有条件地调用(实例引用)不同类型的乘法器
//根据参数ao_width和al_width的值,在调用时引用相对应的乘法器实例
generate
if (a0_width <8)||(a1_width < 8)
   cla_multiplier # (ao_width,a1_width) mo (product,a0,ai);
else
   tree_multiplier #(ao_width, 'a1_width)mo · (product,a0,al);
endgenerate //生成块的结束

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
case生成语句
//根据总线的位宽、调用(实例引用)相应的加法器
//参数卫在调用(实例引用)时可以重新定义
//调用(实例引用)不同位宽的加法器是根据不同的N来决定的generate
case (N)
//当N=1或2时分别选用位宽为1位或2位的加法器
l: adder_1bit adder1(co,sum,a0,al, ci) ; // 1位的加法器
2: adder_2bit adder2(co,sum,a0, a1,ci) ; //2位的加法器
//默认的情况下选用位宽为N位的超前进位加法器
default: adder_cla #(N) adder3(co,sum,a0,a1,ci);
endcase
endgenerate //生成块的结束

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2022.11.19更新

运算符+:和-:补充

变量[起始地址 +: 数据位宽] ------》 变量[(起始地址+数据位宽-1):起始地址]
变量[结束地址 -: 数据位宽] -------》 变量[结束地址:(结束地址-数据位宽+1)]

uart_bytes_data_reg <= {uart_sing_data,uart_bytes_data_reg[(BYTES*8-1)-:(BYTES-1)*8]};	
  • 1
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/784482
推荐阅读
相关标签
  

闽ICP备14008679号