当前位置:   article > 正文

FPGA入门学习(一):Verilog基本语法学习_fpga assign

fpga assign

写在前面:自学笔记,推荐小伙伴一起学习,欢迎提出宝贵意见。参考内容有Verilog菜鸟教程,徐文波版《Xilinx FPGA开发实用教程》;

目录

一、Verilog的基本语法

1.1 Verilog模块的基本概念

1.2 模块内容

1.2.1 I/O说明

1.2.2 内部信号说明

1.3 数值表示

数值种类

整数数值表示方法

1.4 阻塞赋值与非阻塞赋值

1.5 运算符和表达式

1.6 描述语句

1.6.1 结构描述形式

1.6.2 数据流描述形式(assign)

1.6.3 行为描述形式

过程结构(initial、always)

语句块(begin...end/fork...join)

时序控制        

流控制(if、case、for、while、forever、repeat)

二、 Testbench编写

2.1 模块例化

一、Verilog的基本语法

1.1 Verilog模块的基本概念

        Verilog程序是由一个或者多个模块组成的,每个模块可以实现特定的功能,在设计时应该优先确定每个模块的功能,之后在进行代码编写。每个模块要进行端口定义,并说明输入、输出口,然后对模块的功能进行描述,即代码的编写。Verilog程序包括4个主要部分:端口定义、I/O说明、内部信号说明、功能定义。下面的代码即为一个模块:

  1. //二选一多路选择器为例
  2. module module_name(out, a, b, sl);
  3. input a, b, sl; //定义输入信号
  4. output out; //定义输出信号
  5. reg out; //定义存储器
  6. always @(sl or a or b) //语法体
  7. if(!sl)
  8. out = a;
  9. else
  10. out = b;
  11. endmodule
  12. /*module_name是模块名字,在定义时替换成合适的名字,字母和下划线,举例:uart_xx;*/
  • module起始,以endmodule结尾,其内部每个语句和数据定义的最后必须有分号(;)。
  • 每个文件只包含一个module,而且module名要小写,并且与文件名保持一致;
  • 不要书写空的模块,即:一个模块至少要有一个输入和一个输出;

暂时这么多,之后会补充


写在前面,关于Verilog的代码特点

  1. 区分大小写;
  2. 书写自由,一行可以写多个语句,一个语句也可以分行;
  3. 空白符(换行、制表、空格)都没有实际的意义,在编译阶段可忽略。

1.2 模块内容

        在模块的端口声明了模块的输入输出口,其格式如下:

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

这里只是声明了输入输出口,接下来需要具体指明每个端口是输入还是输出。

1.2.1 I/O说明

输入口—— 模块从外界读取数据的接口,在模块内不可写。

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

                input   [7:0]  a;  //说明了一个8位的输入端口 "a"

输出口——模块往外界送出数据的接口,在模块内不可读。

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

                 output   [7:0]  out;  //说明了一个8位的输入端口 "out"

输入/输出口——可读取数据也可以送出数据,数据可双向流动。

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

                inout   [7:0]  in_out;  //说明了一个8位的输入/输出端口 "in_out"

注意:如果不定义位宽则默认位1

1.2.2 内部信号说明

线网(wire):wire 类型表示硬件单元之间的物理连线,由其连接的器件输出端连续驱动。如果没有驱动元件连接到 wire 型变量,缺省值一般为 "Z"。(模块内的连接线)
        wire [7:0]   addr ;       //声明8bit位宽的线型变量addr,

寄存器(reg):寄存器(reg)用来表示存储单元,它会保持数据原有的值,直到被改写。(模块内的寄存器)

        reg [7:0]   addr ;       //声明8bit位宽的线型变量addr,

        reg [15:0]   ROMA   [7:0] ;       //声明了一个存储位宽为16位,存储深度为8的一个存储器。该存储器的地址范围是0到8。(memory型)

常量(parameter):定义常数(常量)。parameter  参数名1 = 数据;

        parameter  [3:0] S0 = 4'h0;   

1.3 数值表示

数值种类

Verilog HDL 有下列四种基本的值来表示硬件电路中的电平逻辑:

  • 0:逻辑 0 或 "假"
  • 1:逻辑 1 或 "真"
  • x 或 X:未知
  • z 或 Z:高阻

x 意味着信号数值的不确定(这里不区分大小写),即在实际电路里,信号可能为 1,也可能为 0。z 意味着信号处于高阻状态,常见于信号(input, reg)没有驱动时的逻辑结果。例如一个 pad 的 input 呈现高阻状态时,其逻辑值和上下拉的状态有关系。上拉则逻辑值为 1,下拉则为 0 。

整数数值表示方法

整技术表示格式:[长度] ' 基数  数值;

  1. 4'b1011 // 4bit 数值
  2. 5'o8 // 5位八进制数
  3. 9'd6 // 9位十进制数
  4. 32'h3022_c0de // 32bit 的数值
  5. /*不区分大小写;下划线是为了增加可读性; */
  6. //一般直接写数字时,默认为十进制表示,例如下面的 3 种写法是等效的:
  7. counter = 'd100 ; //一般会根据编译器自动分频位宽,常见的为32bit
  8. counter = 100 ;
  9. counter = 32'h64 ; //16进制64 == 十进制100

负数表示:通常在表示位宽的数字前面加一个减号来表示负数,

  1. -6'd15
  2. -15
  3. 4'd-2 //非法说明,错误

        其中负数使用补码形式,-15 在 5 位二进制中的形式为 5'b10001(补码:取反加一为实际值,最高位1表示符号位,0001取反加一等于1111), 在 6 位二进制中的形式为 6'b11_0001(最高位1为符号扩展位)。

注意:减号放在基数和数字之间是非法的,

实数:实数可以用两种形式定义:十进制计数法和科学计数法。

  1. 1.十进制计数法
  2. 2.0
  3. 3.1415926
  4. 2.科学计数法
  5. 235.12e2 实际值23512
  6. 5e-4 实际值0.0005
  7. 其中e与E相同,根据Verilog语法定义,实数通过四舍五入隐式的转换为最相近的整数

字符串:字符串是双引号内的字符序列,字符串不能分成多行书写,用8位ASCII值表示的字符可看作是无符号整数,一个ASCII码占8位。

  1. reg [1:8*7] Char;
  2. Char = "counter";

1.4 阻塞赋值与非阻塞赋值

1.5 运算符和表达式

1.6 描述语句

1.6.1 结构描述形式

1.6.2 数据流描述形式(assign)

        数据流型描述一般都采用(assign)连续赋值语句来实现,主要用于实现组合功能。连续赋值语句右边所有的变量受持续监控,只要这些变量有一个发生变化,整个表达式将被重新赋值给左端。这种方法只能用于实现组合逻辑电路。

        assign  L_s = R_s;

  1. //一个利用数据流描述的移位器
  2. module mlshift2 (a, b)
  3. input a;
  4. output b;
  5. assign b == a << 2;
  6. endmodule
  7. //只要a值发生变化,b就会被重新赋值,所赋的值为a左移两位后的值

1.6.3 行为描述形式

        行为级描述主要包括过程结构、语句块、时序控制、流控制4个方面,主要用于时序逻辑功能的实现。(个人以为只需要了解关键字的意义即可)

过程结构(initial、always)
  • initial 模块        

        initial模块从模拟0时刻开始执行,且在仿真过程中只执行一次,在执行完一次后,该initial就被挂起,不在执行。如果仿真中有多个initial模块,则同时从0时刻开始并行执行。(initial模块是面向仿真的,是不可增和的,通常被用来测试模块初始化、监视、波形生成等功能)

  1. initial begin
  2. //初始化输入
  3. clk = 0;
  4. ai = 0;
  5. bi = 0;
  6. //等待100ns,全局reset信号有效
  7. # 100;
  8. ai = 20;
  9. bi = 10;
  10. end
  • always 模块

        always模块是一只重复执行的,并且可被综合。敏感事件表的目的就是触发always模块的运行,而initial后面是不允许有敏感事件表的,always过程块的触发条件是:只要a、b、c信号的电平有任意一个发生变化。

  1. always @(a or b or c) begin
  2. ...
  3. end

        always模块主要是对硬件功能的行为进行描述,可以实现锁存器和触发器,也可以用来实现组合逻辑。利用always实现组合逻辑时,要将所有的信号放进敏感列表,而实现时序逻辑时却不一定要将所有的结果放进敏感信息列表。(上例中只有a、b、c中存在变化时就会执行语句块,如果存在其他的信号,比如e发生变化则不会执行语句块)

语句块(begin...end/fork...join)

        语句块就是initial模块或者always模块中位于begin...end/fork...join块定义语句之间的一组行为语句。

  1. begin ... end 串行块,用来组合需要顺序执行的语句
  2. fork ... join 并行块,用来组合需要并行执行的语句
  3. //例:下面两段程序都是用来产生一系列延迟波形
  4. parameter d = 50;
  5. reg [70] r;
  6. begin
  7. # d r = 'h35; //语句1
  8. # d r = 'h35; //语句2
  9. # d r = 'h35; //语句3
  10. # d r = 'h35; //语句4
  11. # d -> end_wave; //语句5,触发事件end_wave
  12. end
  13. parameter d = 50;
  14. reg [70] r;
  15. fork
  16. # d r = 'h35; //语句1
  17. # 2d r = 'h35; //语句2
  18. # 3d r = 'h35; //语句3
  19. # 4d r = 'h35; //语句4
  20. # 5d -> end_wave; //语句5,触发事件end_wave
  21. join
  22. //由于并行块中语句是同步进行的,所以为了产生间隔d的波形需要递增延时时间
时序控制        
  • 延时控制        always #10 Clk = ~Clk; 延时控制只能在仿真时使用,是不可综合的。在综合时所有的延时控制都会被忽略。
  • 事件控制        边沿触发事件与电平触发事件
  1. //边沿触发事件计数器(计算时钟变化次数)
  2. reg [4:0] cnt;
  3. always @(posedge Clk or negedge Reset_n)begin //posedge上升沿关键字 negedge下降沿关键字
  4. if(!Reset_n)
  5. cnt <= 0;
  6. else
  7. cnt <= cnt + 1;
  8. end
  9. //电平触发事件计数器(记录a,b,c变化的次数)
  10. reg [4:0] cnt;
  11. always @(a or b or c)begin
  12. if(!Reset_n)
  13. cnt <= 0;
  14. else
  15. cnt <= cnt + 1;
  16. end
  17. ???如果a,b,c同时变化只记录一次???
流控制(if、case、for、while、forever、repeat)

流控制语句包含3类,即跳转、分支、循环语句。

  1. //if语句
  2. always @(a1 or b1)begin
  3. if(a1) q <= d;
  4. else q <= 0 //else分支也可以默认,但会产生一些不可预料的结果,生成成本不期望的锁存器。
  5. end
  6. //case语句
  7. always @(a1[1:0] or b1)begin
  8. case (a1)
  9. 2'b00 : q <= b1;
  10. 2'b01 : q <= b1 + 1;
  11. default : q <= b1 + 2; //default分支虽然可以默认,但一般不要默认,可能会生成锁存器。
  12. end
  13. //for循环
  14. for(i = 1; i <= size; i = i + 1)
  15. result = result + (a << (i - 1));
  16. //while循环
  17. while (temp)begin
  18. count = count + 1;
  19. end
  20. //forever循环
  21. /*forever必须写在initial模块中,用于产生周期性波形,系统函数 $finish 可退出 forever。*/
  22. initial forever begin
  23. if (d) a = b + c;
  24. else a = 0;
  25. end
  26. //repeat循环
  27. repeat (size)begin //指定循环次数
  28. c = b << 1;
  29. end

二、 Testbench编写

2.1 模块例化

        在一个模块中引用另一个模块,对其端口进行相关连接,叫做模块例化(一个模块其实就是一个器件,将器件连接起来的操作就是例化)。模块例化建立了描述的层次。信号端口可以通过位置或名称关联,端口连接也必须遵循一些规则。如果某些输出端口并不需要在外部连接,例化时 可以悬空不连接,甚至删除。一般来说,input 端口在例化时不能删除,否则编译报错,output 端口在例化时可以删除。建议 input 端口不要做悬空处理,无其他外部连接时赋值其常量,

        当例化端口与连续信号位宽不匹配时,端口会通过无符号数的右对齐或截断方式进行匹配。子模块的端口信号位宽大于例化对应端口,则截断;反之补位右对齐。

点击阅读上面例子的原文

用 generate 进行模块例化


 
声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号