• FPGA数字电子技术复习笔记(一)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
    //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
  • 相关阅读:
    西安凯新(CAS:2408831-65-0)Biotin-PEG4-Acrylamide 特性
    ThreadGroup
    初识Rasp——Openrasp代码分析
    基于Xml方式Bean的配置-Bean的延时加载
    [云] 大数据分析栈(Big Data Analytics Stack)+ Apache Hadoop分布式文件系统(HDFS)+Apache Spark
    qtcreator的快捷键
    linux服务器在后台运行程序
    【Arduino31】LED与步进电机实验
    LeetCode每日一题(2310. Sum of Numbers With Units Digit K)
    如何开通腾讯云Redis云数据库并创建连接?
  • 原文地址:https://blog.csdn.net/zerokingwang/article/details/127277398