当前位置:   article > 正文

FPGA自学1——Verilog基础语法

fpga自学

       从今天开始自学FPGA,自学过程中感受较深的是,FPGA的编程不止是写代码,代码实际是一个电路,这点是与单片机编程概念不同的。 

        代码的好坏直接影响的是一个硬件电路

1、基础知识

  • 逻辑0:低电平,也就是电路中的GND
  •  逻辑1:高电平,也就是电路中的VCC
  • 逻辑X:未知状态,
  • 逻辑Z:高阻态,外部没有激励信号,是一个悬空状态

 2、数字进制格式、标示符

b:表示二进制        o:表示八进制                d:表示十进制                h:表示十六进制

  1. numA=8'b10101010; //8'表示numA这个变量占8位,numA的值是二进制的10101010
  2. numB=4'd2;//numB的值是十进制的2
  3. numC=4'ha;//numC的值是十六进制的A
  4. numD=10;//numD占32bit,是十进制的10

说明:不指定位宽则默认是32位,不指定进制则默认是十进制

 标示符:可用于模块名、端口号、信号名等(类似C的变量名

3、数据类型 

  • 寄存器数据类型 (reg)
  • 线网数据类型(wire)
  • 参数数据类型(parameter)

3.1寄存器数据类型

关键则:reg       初始值为:不定制X(逻辑X)

  1. module test(L);
  2. output LED;
  3. input num;
  4. reg[7:0] LED; //寄存器数据类型
  5. reg num; //寄存器数据类型
  6. always
  7. begin
  8. LED=8'b10101010;
  9. end
  10. endmodule

3.2 线网数据类型

关键字:wire      表示结构实体之间的物理连线

线网类型的变量不能村初值,他是由驱动她都元件决定的

没有驱动元件连接的线网类型变量是:高阻值(逻辑Z)

wire key_flag;

3.3 参数类型

关键字:parameter 

他类似C中的 #define,宏定义

4、运算符

Verilog中的 算数运算符、关系运算符、逻辑运算符、条件运算符、位运算符、移位运算符包括其 运算符优先级 也与C语言相同。

 Verilog特有的拼接运算符:{}

C={a,b[3:0]}   //将变量a与变量b的低四位拼接起来,赋值给变量C

5、模块

Verilog的基本设计单元是“模块”(类似C语言中的函数)

一个模块由两部分组成

  1. 描述接口
  2. 描述逻辑接口

一个模块的示例: 

  1. module test(LED,a,b,c,d,e); //模块开始的关键字是:module 括号里的都是:端口定义
  2. output LED,c,d; //输出信号 input output 都是:IO说明
  3. input a,b; //输入信号
  4. reg[7:0] LED; //寄存器数据类型
  5. reg e; //寄存器数据类型
  6. wire f; //线网数据类型 f:内部信号
  7. //变量声明结束后,都是:功能定义
  8. assign c=a|b;
  9. assign d=a&b; //assign :产生wire信号语句的关键字 描述组合逻辑
  10. always begin //always:产生reg信号语句的关键字 描述组合、时序逻辑
  11. LED=8'b10101010;
  12. end
  13. endmodule //模块要用“endmodule”结束

 与C语言不同的是,Verilog中多个<always>块都是“并行”的 ,也就是说,两个<always>块可以同时被执行

单个<always>块是中的代码是顺序执行的。

   assign相当于连线,一般是将一个变量的值不间断地赋值给另一个变量,就像把这两个变量连在一起,所以习惯性的当做连线用,比如把一个模块的输出给另一个模块当输入。

       assign的功能属于组合逻辑的范畴,应用范围可概括为以下三点:

  • 持续赋值;
  •  连线;
  •  对wire型变量赋值,wire是线网,相当于实际的连接线,如果要用assign直接连接,就用wire型变量。wire型变量的值随时变化。其实以上三点是相通的。

       要更好的把握assign的使用,Verilog中有几个要点需要深入理解和掌握:

  1. 在Verilog module中的所有过程块(如initial块和always块)、连续赋值语句(如assign语句)和实例引用都是并行的。在同一module中这三者出现的先后顺序没有关系。
  2. 只有连续赋值语句assign和实例引用语句可以独立于过程块而存在于module的功能定义部分。
  3. 连续赋值assign语句独立于过程块,所以不能在always过程块中使用assign语句
     

  1. module LED_Blink( //可以在端口定义时 指定IO
  2. input sys_clk, //系统时钟
  3. input sys_rst_n, //系统复位
  4. output reg[3:0] led;//4bit 代表四颗灯
  5. );
  6. reg [23:0] count; //计数 (这是一个内部信号声明)
  7. //计数器对系统时钟计数,0.2s
  8. /*赋值“=”用于阻塞式赋值(执行到该行时,等待赋值完成再执行下一条语句),仿真是initial中用=;
  9. “<=”用于非阻塞式赋值中(执行到改行时,不等待赋值完成,直接执行下一条语句),always中用<=
  10. */
  11. always @(posedge sys_clk or negedge sys_rst_n)begin//posedge:上升沿触发 negedge:下降沿触发
  12. if(!sys_rst_n)
  13. count<=24'd0; //对Count1赋23位十进制的0
  14. else if(count<24'd1000_0000)
  15. count<=count+1'b1;
  16. else
  17. count<=24'b0;
  18. end
  19. endmodule

赋值“=” 用于阻塞式赋值(执行到该行时,等待赋值完成再执行下一条语句),仿真是initial中用=;
赋值“<=”用于非阻塞式赋值中(执行到改行时,不等待赋值完成,直接执行下一条语句),always中用<=    

 6、模块的调用

在模块调用时,信号通过模块端口在模块之间传递。

  1. module seg_led_static_top( //上层模块
  2. input sys_clk, //系统时钟
  3. input sys_rst_n, //系统复位
  4. output [5:0] sel;
  5. output [7:0] sel_led;
  6. );
  7. parameter TIME_SHOW=25'd25000_000; //宏定义
  8. wire add_flag; //数码管变化通知
  9. //# 是延迟的意思,井号后面数字是延迟的数量
  10. //#1 a=1;#表延迟,延迟一个时间单位后执行a=1
  11. time_count #( //这里的#表示:用来将parameter变量传给调用实例。
  12. .MAX_NUM (TIME_SHOW)
  13. //上层模块传输给底层模块
  14. )
  15. u_time_count(
  16. .clk (sys_clk) // .表示:连接 将上层模块的sys_clk与下层模块clk连接在一起
  17. .rst_n (sys_rst_n)
  18. .flag (add_flag)
  19. )
  20. endmodule
  21. module time_count( // 底层模块
  22. input clk, //系统时钟
  23. input rst_n, //系统复位
  24. output reg flag;
  25. );
  26. parameter MAX_NUM=25'd50000_000; //宏定义
  27. reg [24:0] cnt;
  28. endmodule

# 是延迟的意思,井号后面数字是延迟的数量  
例如代码    :       #1 a=1;    //延迟一个时间单位后执行a=1

#():这里的#表示:用来将parameter变量传给调用实例。

.是连接的意思:将上层模块的变量与下层模块变量连接在一起

7、结构语言

7.1 always 和 initial

initial 语句只执行一次

always     语句是一直不断重复的

  1. //initial 语句只执行一次
  2. //always 语句是一直不断重复的
  3. initial begin
  4. sys_clk=10;
  5. b=11;
  6. c=12;
  7. #20 d=13;
  8. end
  9. //赋值“=”用于阻塞式赋值(执行到该行时,等待赋值完成再执行下一条语句),仿真是initial中用=;
  10. always #10 sys_clk<=~sys_clk; //chans 50Mhz的时钟,周期为20ns
  11. //“<=”用于非阻塞式赋值中(执行到改行时,不等待赋值完成,直接执行下一条语句)always中用<=

        always的时间就控制可以是沿触发,也可以是电平触发

  •   沿触发的always块常用于表示时序逻辑行为
  1. always @(posedge sys_clk or negedge sys_rst_n)begin//posedge:上升沿触发 negedge:下降沿触发
  2. if(!sys_rst_n)
  3. count<=24'd0; //对Count1赋23位十进制的0
  4. else if(count<24'd1000_0000)
  5. count<=count+1'b1;
  6. else
  7. count<=24'b0;
  8. end

                  or 连接的多个时间或者信号组成的列表称为:敏感列表

                 只有敏感列表里的条件满足时,才会执行此always模块

  • 电平触发的always模块表示组合逻辑行为
  1. always @(*)begin
  2. out1=a?(b+c):(d+e);
  3. end
  4. //@(*):模块中所有使用的输入变量都是敏感的

                 @(*):模块中所有使用的输入变量都是敏感的

  • 组合逻辑:输出只取决于输入
  • 时序逻辑:输出取决于输入和原电路状态

7.2 阻塞赋值和非阻塞赋值 

  • 阻塞赋值:只有一个步骤的操作,即执行到该行赋值语句时,要等待赋值完成再执行下一条语句,是一个顺序执行的串行方式。(这点与C语言一致)

        假设 a=1,b=2,C=3 执行      a=0;b=a;c=b;    后  a b c的值都为0

  • 非阻塞赋值:多个步骤同时的操作,即执行到改行时,不等待赋值完成,直接执行下一条语句,是一个顺序执行的并行方式。

        假设 a=0,b=2,C=3 执行      a<=0;b<=a;c<=b;    后  a =0   b=1  c=2

        描述组合逻辑的always语句用“阻塞赋值=”

        描述时序逻辑的always语句用“非阻塞赋值<=”

注意:1.在同一个always模块中不同时使用 非阻塞赋值  和 阻塞赋值 

            2.     不允许在多个always块中一个变量进行赋值 ,因为Verilog是并行执行的

         阻塞赋值和非阻塞赋值产生的实际电路区别:

         当存在多条赋值语句时,阻塞赋值会产生多个级联的触发器.

7.3 条件语句

     if....else           

        if .........else if..........    用法和C语言相同。 

  • if语句中的条件,为1则表示真,若为0 X Z都按假处理 
  • if   else 后面可以用begin 和end包含多个语句,类似与C语言中 if后面的大括号

if......esle..........是带有优先级的,实际产生的电路如下: 

   case语句

  •  分支表达式的值互不相同
  •  所有表达式的位宽必须相等(必须定位宽)
  •  caseZ不适用于高阻值Z  (casez)
  •  casex    下列代码  语句1 和语句2 都不执行

                              

case是没有优先级的  实际电路如下:

7.4 循环

7.4.1:forever:连续循环执行

        一般只用于仿真测试 

7.4.2:repeat 执行固定次数循环

7.4.3 while循环

 

        while循环与C语言相同,但是Verilog中只用于仿真调试 

7.4.4 for循环 

        与C语言相同,实际产生的是一个多层级的物理电路,for循环在Verilog中比较常用。

 7.5 函数

        函数通常为一系列组合逻辑,有返回值

        函数关键字:function

函数定义:

  1. //下面相当于函数声明
  2. function [15:0]mult;//函数名
  3. input[7:0] a,b;
  4. reg [15:0] r;
  5. integer i; //整数(integer),integer类型的变量为有符号数
  6. //下面相当于函数主体
  7. begin
  8. if(a[0]==1)
  9. r=b;
  10. else
  11. r=0;
  12. for (i =1 ;i<=7 ;i=i+1 ) begin
  13. if(a[i]==1)
  14. r=r+b<<1;
  15. end
  16. mult=r; //返回值就是函数名
  17. end
  18. endfunction

函数调用:

  1. module
  2. mult_acc(
  3. input[7:0] ina,inb;
  4. )
  5. wire[15:0]mult_out;
  6. assign mult_out=mult(ina,inb);//函数调用
  7. endmodule

 

  7.6 任务

任务关键字:task

  1. module module_name
  2. task add_num; //任务定义
  3. input a,b;
  4. output c;
  5. begin
  6. c=a+b;
  7. end
  8. endtask
  9. reg num;
  10. add_num(1,2,num) //任务调用
  11. endmodule

函数 与任务的区别 

function        task
  • 在时间0开始执行
  •  执行过程中不能暂停
  • 不能包含延时、时间、或者其他 时间控制操作
  • 可在非0仿真时间执行
  • 可包含延时、时间或其他时间控制操作
必须包含至少一个输入                        可以没有输入、输出
不能使用输出/双向参数 (类似C的形参带出返回值),只能返回1个值,返回变量与函数名相同使用输出参数、可以返回多个值
函数可以调用另一个函数,但是不能调用任务任务可以调用另外的函数或者任务。

 8、状态机(FSM)

 状态机:在有限个状态之间按一定规律转换的时序电路

  •  mealy状态机 :  输出与当前状态和输入有关   (时序逻辑的类似
  •  moore状态机:  输出只与输入有关(组合逻辑类似

状态机模型中由  两个组合逻辑       和     一个时序逻辑     组成

8.1设计状态机 

 以下的设计步骤:

  1. 状态空间定义
  2. 状态跳转
  3. 下个状态判断
  4. 各个状态的动作

 8.1.1状态空间定义

是一个定义的过程

  1.  宏定义出状态的名称 和数值
  2. 定义当前状态和下一个状态的存储变量(变量要与上面定义的数值位数相同)

      推荐每一个状态只有寄存器的一个位置,这样编译逻辑简单,编译后的硬件电路简单

 8.1.2状态跳转

状态跳转是一个时序逻辑 

        

  再次强调:时序逻辑中使用 非阻塞赋值

 8.1.3下一个状态的判断

根据输入状态判断输出状态,是一个组合逻辑

 再次强调:组合逻辑中使用 阻塞赋值

 latch:是一个锁存器,程序设计中要避免latch的产生,他会使我们电路输出的信号产生毛刺

(if else 没有配对   case中没有default 就会产生latch)

 8.1.4下一个状态的动作

根据当前状态完成当前动作,是一个组合逻辑

下面是产生组合逻辑的两种方式: 

 always:可以表示时序逻辑  和 组合逻辑

 

  mealy状态机 后面又加了一级寄存器来实现时序逻辑的输出,这么做的好处是

  1. 可以在有效的滤去组合逻辑输出的毛刺
  2. 可以有效的进行时序计算和约束
  3. 对于总线型的输出信号来说,容易使总线对齐,从而减小总线数据见的便宜,减小接收端数据采样的出错频率

9、同时执行

        举例有代码如下:

        在一个clk时钟的上升沿到来时,always语句块里的代码同时被执行。

        S_in1里的值是上一时刻S_in0的值,不会是这一时刻S_in的值。

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

闽ICP备14008679号