目录
基于正点原子的STM32 FPGA视频讲解笔记。。。。。
基础知识
逻辑0:表示低电平,也就对应我们电路GND;
逻辑1:表示高电平,也就是对应我们电路VCC;
逻辑X:表示未知,有可能是高电平,也有可能是低电平;
逻辑Z:表示高阻态,外部没有激励信号,是一个悬空状态。
Verilog数字进制包括二进制、八进制、十进制和十六进制。常用为二进制、十进制、十六进制。
二进制:4’b0101表示4位二进制数字0101;
十进制:4’d2表示4位十进制数字2(二进制为0010)
十六进制:4’ha表示4位十六进制数字a(二进制1010)
注意:如果没有在进制数前面标明是多少位的,系统默认为32位,即32’。
在Verilog语句中可以将进制数字用_隔开,例如:
16’b1001_1010_1010_1001
系统会自动将下划线忽略进行缩进。
标识符
用于定义模块名、端口名、信号名等。
标识符可以是任意一组字母、数字、$符号和_符号的组合;
但标识符的第一个字符必须是字母或者下划线;
标识符是区分大小写的;
标识符推荐写法:
- 不建议大小写混合使用;
- 普通内部信号建议全部小写;
- 信号命名最好体现信号的含义,简洁、清晰、易懂;
例如:
- 用有意义的有效的名字如sum、cpu_addr等。
- 用下划线区分词,如cpu_addr。
- 采用一些前缀或后缀,比如时钟采用clk前缀:clk_50,clk_cpu;
数据类型
Verilog语言中主要有三大数据类型:
寄存器数据类型、线网数据类型和参数数据类型。
从名称中,我们可以看出,真正在数字电路中起作用的数据类型应该是寄存器数据类型和线网数据类型。
寄存器
寄存器表示一个抽象的数据存储单元,通过赋值语句可以改变寄存器存储的值
寄存器数据类型的关键字是reg,reg类型数据的默认初始值为不定值x
reg类型的数据只能在always语句和initial语句中被赋值。
如果该过程语句描述的是时序逻辑,即always语句带有时钟信号,则该寄存器变量对应为触发器;
如果该过程语句描述的是组合逻辑,即always语句不带有时钟信号,则该寄存器变量对应为硬件连线;
线网类型
线网数据类型表示结构实体(例如门)之间的物理连线。
线网类型的变量不能存储值,它的值是由驱动它的元件所决定的。
驱动线网类型变量的元件有门、连续赋值语句、assign等。
如果没有驱动元件连接到线网类型的变量上,则该变量就是高阻的,即其值为z。
线网数据类型包括wire型和tri型,其中最常用的就是wire类型。
参数类型
参数其实就是一个常量,在Verilog HDL中用parameter定义常量。我们可以一次定义多个参数,参数与参数之间需要用逗号隔开。每个参数定义的右边必须是一个常数表达式。
例如:
Parameter H_SYNC = 11’d41; //行同步
类似于C语言中的宏定义(define)。
参数型数据常用于定义状态机的状态、数据位宽和延迟大小等。采用标识符来代表一个常量可以提高程序的可读性和维护性。在模块调用时,可以通过参数传递来改变被调用模块中已定义的参数。
运算符
算术运算符:
关系运算符:
逻辑运算符:
条件操作符:
这个相当于C语言的三目运算符,可改写成if-else形式。
位运算符:
移位运算符:
两种移位运算都用0来填补移出的空位。
左移时,尾款增加;右移时,位宽不变。
以上的运算符基本上与C语言一模一样,这里就不多说了。
拼接运算符:
例如:c = {a[7:0],b[3:0]};
这个语句意思是将a与b拼接起来,并且a在前b在后形成一个c[11:0]位宽为11。
运算符的优先级:
这里不用死记硬背,括号优先级最高,在不确定时采用括号括起来。
Verilog程序框架
Verilog 注释
Verilog的注释与C语言的一模一样,这里不多说。
Verilog 关键字
常用关键字
模块的结构
Verilog的基本设计单元是“模块”(block)。
一个模块是由两部分组成的,一部分描述接口,另一部分描述逻辑功能。
例如:
该段语句所描述的内容转换成数字电路如下:
以下为流水灯历程:
一开始先命名模块名,模块名尽量见名知意,模块中有三个端口,可以在模块名()中定义端口,在led端口中定义其为寄存器类型,位宽为4位。
注意:在不指定端口是什么类型时系统自动将其默认为线网类型(wire)。
下面还定义了一个寄存器类型的变量,位宽24位,命名为counter,由名字知道它是一个24位的计数器。
功能定义部分有三种方法:
- assign语句
描述组合逻辑
- always语句
描述组合/时序逻辑
- 例化实例元件
如:and#2 u1(q,a,b);
以上三种逻辑功能是并行的
注意:在always块中,逻辑是顺序执行的,而多个always块之间是并行的。
顺序执行的部分可以用C语言的编程思想来做。
模块的调用
模块调用的原理与c语言函数的调用类似。
在模块调用时,信号通过模块端口在模块之间传递。
以静态数码管代码举例如下:
模块命名时以后缀top结尾,表示顶层模块
下面我们会例化一个模块
以下为time_count模块代码
端口定义其中包括两个输入信号和一个输出信号,还有一个数据类型的变量MAX_NUM,其值为25000000.
例化时需要将其用不同名称代替这个模块
然后用括号内的语句将底层模块的端口与顶层模块的端口进行连接
然后再将顶层模块的参数类型与底层模块的参数类型进行连接
这样我们就将顶层模块与底层模块进行对接了。
这里参数类型变量TIME_SHOW在顶层模块定义为
这里对接好后会将顶层模块的参数类型变量的值直接传导到底层模块,参数值主要以顶层模块参数为主。如果我们要改变参数类型变量的参数就可以直接在顶层模块进行修改,不需要到底层模块去改,大大提高了灵活性。
这里要注意,模块信号的输入端传递的信号可以是wire型的,也可以是reg型的,模块信号的输出端中断口类型一定要是wire型的,不然会报错!!!
模块的输入输出端在模块调用时要统一位宽!!!
模块调用的另一种方法:
这里的顶层模块端口要非常严格的与底层模块端口对应,不建议采用这种方法。
这里还调用了一个模块,模块名叫做u_seg_led_static
综合后生成的电路原理图如下:
这里我们可以清楚的看出顶层模块端口
还有底层模块的调用,也可明显看出在顶层模块调用底层模块时的命名。
Verilog高级知识点
结构语句
Initial和always
Initial语句它在模块中只执行一次,相当于C语言中的static语句。
它常用于测试文件的编写,用来产生仿真测试信号(激励信号),或者用于对存储器变量赋初值。
Always语句一直在不断地重复活动,相当于C语言中的while(1)语句。
但是只有和一定的时间控制结合在一起才有作用。
例子:
以上就是Verilog代码与它的仿真图,一开始我们将sys_clk、sys_rst_n、touch_key赋初值,这里用到(<=)非阻塞赋值,后面会讲到,意思为这三个变量赋值是同时进行的,没有先后顺序,#20意思是延时20ns,延时20ns后将sys_rst_n拉高,当这条语句结束后才会进入下一个语句,即#10 延时10ns后吧touch_key拉高,然后再按顺序执行下去,直到end结束,整个过程只执行一次。
Always块中也是同样的道理,只不过它是一直循环执行,#10延时10ns后将sys_clk变量取反一次,如此循环形成一个固定频率的时钟脉冲信号。
这里的initial语句和always语句没有顺序之分,都是同时进行的!!!
Always的时间控制可以是沿触发,也可以是电平触发:
可以是单个信号,也可以是多个信号,多个信号中间要用关键字or连接。
Always语句后紧跟的过程块是否运行,要看它的触发条件是否满足。
上面代码中posedge表示上升沿,negedge表示下降沿,意为当sys_clk为上升沿或者sys_rst_n为下降沿时执行以下块语句,begin—end中的语句就是我们熟悉的C语言if—else语句,块语句中是有顺序之分的,编程思想与C相同。
沿触发的always块常常描述时序逻辑行为。
由关键词or连接的多个事件名或信号名组成的列表称为“敏感列表”。
电平触发的always块常常描述组合逻辑行为。
但这种写法容易繁琐容易出错,推荐下列写法:
@(*)表示对后面语句块中所有输入变量的变化都是敏感的。
根据逻辑功能的不同特点,可以将数字电路分成两大类:
组合逻辑电路和时序逻辑电路。
组合逻辑电路中,任意时刻的输出仅仅取决于该时刻的输入,与电路原来的状态无关。时序逻辑电路中,任一时刻的输出不仅取决于当时的输入信号,而且还取决于电路原来的状态。或者说还与以前的输入有关,因此时序逻辑必须具备记忆功能。
简而言之时序逻辑电路必须要有一个存储器来存储它原有的状态,而组合逻辑是不需要的,是直接触发的。
赋值语句
信号的两种赋值方式:
阻塞赋值
如:b=a;
所谓阻塞的概念是指在同一个always块中,后面的赋值语句是在前一句赋值语句结束后才开始赋值的。
我们看到以下阻塞赋值语句和波形图。
其实这不难理解,跟C语言一样,就是让赋值语句有了顺序,编程思想与C完全一致。
非阻塞赋值
如:b<=a;
非阻塞赋值的操作过程可以看作两个步骤:
- 赋值开始的时候,计算RHS;
- 赋值结束的时候,更新LHS。
所谓非阻塞的概念是指在计算非阻塞赋值的RHS以及更新LHS期间,允许其他的非阻塞赋值语句同时计算RHS和更新LHS。
看到以下代码与波形图:
简单的说就是无序赋值,并且是将所有变量赋值好后才更新,例如a<=0;b<=a;c<=0这段中就是先将数值暂存,这时的a,b,c变量的值是不变的,依旧是1、2、3,直到end结束后才更新abc的值。
注意:非阻塞赋值只能用于对寄存器类型的变量进行赋值,因此只能用在initial块和always块等过程块中。
使用建议:
在描述组合逻辑的always块中用阻塞赋值=,综合成组合逻辑的电路结构;
这种电路结构只与输入电平的变化有关系。
在描述时序逻辑的always块中用非阻塞赋值<=,综合成时序逻辑的电路结构;
这种电路结构往往与触发沿有关系,只有在触发沿时才可能发生赋值的变化。
注意:在同一个always块中不要既用非阻塞赋值又用阻塞赋值
不允许在多个always块中对同一变量进行赋值!
条件语句
If-else语句
这个跟C一样,没什么好说的。
这个跟C有点不一样,如果在if中想要执行多个语句,C采用{}括起来,这里是采用begin-end等同于C的{};
条件语句是允许嵌套的。
If语句条中遇到0,x,z统统按假处理。
注意:条件语句必须在过程块中使用。也就是initial和always过程块。
Case语句
这里跟C不一样,没有switch,只有case,并且符合条件的语句只执行一次就退出,没有break语句,default语句跟C一样。
注意:
- 所有表达式的位宽必须相等;不能用’bx来代替n’bx
- casez
比较时不需要考虑表达式中的高阻值。
比如这里的sel,值为1100_0011,只需要跟表达式中高四位匹对就行,不需要考虑低四位。
- casex
即不考虑高阻值z又不考虑不定值x
例如语句2中有不定值和高阻值,这时就不需要考虑他们,只需考虑高四位是否匹对。