作者:MiS603开发团队
日期:20150911
公司:南京米联电子科技有限公司
论坛:www.osrc.cn
EAT博客:http://blog.chinaaet.com/whilebreak
博客园:http://www.cnblogs.com/milinker/
2.2 Verilog HDL硬件语言基础
2.2.1 技术背景
大规模集成电路设计制造技术和数字信号处理技术,近三十年来,各自得到了迅速的发展。这两个表面上看来没有什么关系的技术领域实质上是紧密相关的。因为数字信号处理系统往往要进行一些复杂的数学运算和数据的处理,并且又有实时响应的要求,它们通常是由
高速专用数字逻辑系统或专用数字信号处理器所构成,电路是相当复杂的。因此只有在高速大规模集成电路设计制造技术进步的基础上,才有可能实现真正有意义的实时数字信号处理系统。对实时数字信号处理系统的要求不断提高,也推动了高速大规模集成电路设计制造技
术的进步。现代专用集成电路的设计是借助于电子电路设计自动化(EDA)工具完成的。学习和掌握硬件描述语言(HDL)是使用电子电路设计自动化(EDA)工具的基础。
2.2.2 学习VHDL还是Verilog
笔者建议Verilog,虽然很多学校古董级的老师还在教VHDL.当然VHDL也是要了解的,因为这门古老的语言的历史遗留问题,现在还有很多VHDL的模块,有的时候我们要拿来主义,所以还有必要了解下的。但是历史的车轮总是在前进,优胜劣汰。也许不久的将来Verilog也会被C,C++这种高级语言代替。
为了更方面地切入主题,笔者假设,你已经学过单片机,并且掌握C语言。因为单片机,和C语言,可以说是当代大学生的一项基本能力。有了这个基础,再学习其他现代计算机编程,算法,才能达到事半功倍的效果。如果你还不会单片机和C语言,建议你首先学会单片机,或者C语言。当然,这只是笔者的建议,不会单片机,或者C语言,并不代表学不好Verilog语言。
学过单片机的都知道,我们的程序代码是一条指令一条指令来执行的。CPU首先通过总线,读取一条指令,然后解析这条指令,再然后执行这条指令。我们写的C代码总是一条一条地执行。如果我们同时要处理10个子程序,那么CPU必须一个个子程序来执行。如果其中有一部子程序实时性要求很高,但是其中某些子程序执行起来又耽误非常多的时间,那么可想而知,很可能就没法实现这个功能,或者需要用非常高,非常高的CPU频率。如果有些实时性较高的,如扫描下矩阵键盘,VGA刷个屏,都需要中断来实现。如果刷屏时间比较长,就会影响到你按键的灵敏度。另外比如,我们的单片机在用串口接收数据,并且也要发送数据,同时我们的单片机要处理外部的IO信号,如果我们的IO信号非常快,并且有几百个信号,可能同一个时刻触发,很显然,如果这些信号比较快,那么我们的单片机,就没法实现了。
这是笔者简单举了两种情况,那么如果使用FPGA就可以很方便地解决以上问题。由于FPGA的并行性,不管是扫描键盘,还是扫描VGA,都可以把它们做成独立的模块,,时间上没有冲突,每个模块可以同时执行。
再比如用一个FPGA,就可以同时完成串口的收发,以及IO的监控,因为FPGA的程序实际上就是电路,是瞬间就完成了,我们只要用Verilog写出来相应功能的程序模块,这些模块是同时运行的。
这样看来FPGA真是太强大了,太完美了。但不要高兴地太早,FPGA确实速度很快,瞬间完成,但是如果我们要让他跟单片机能够被我们控制着,一个任务,一个任务来走,那就麻烦了。因为FPGA总是瞬间完成了。由于FPGA可以在一个时钟内,完成多条语句的赋值,但是如果赋值必须有个前后顺序呢?也就是需要一步步的完成,怎么办?如果说并行控制是FPGA的优点,那么顺序控制就是他的不足之处。世界上永远没有完美的东西,我们在获得一种优势的时候,往往也获得了一种劣势。但是,办法总比问题多。
2.2.3 顺序控制的第一种办法——状态机设计
可以说,我们用Verilog来写程序,状态机无处不在。顾名思义,通过设计状态机,我们可可以控制Verilog让他该快的时候快,该慢的时候慢,该做什么的时候就做什么。这才是我们想要的。状态机是很不错的东西,初学者对他望而生畏,而熟悉Verilog语言的人都对其会爱不释手。但是很多时候,我们写代码的人也很痛苦,一个大一点的工程,N多的模块,N多的状态机。
2.2.4 顺序控制的第二种方法——FPGA中运行CPU
FPGA也可以运行CPU?是的,没错,FPGA也可以像单片机一样使用,这样我们就可以用C代码来一条条指令来执行了,这不是太强大了?是的,没错。关键的问题是,我们是可以把一些逻辑控制顺序复杂的事情用C代码来实现,而实时处理的部分仍然用Verilog来实现。并且那部分Verilog可以被C代码控制。Xilinx FPGA目前支持的CPU有Microblaze,ARM9,POWERPC,CortexA9(zynq就Xilinx比较新的一款片子,完美的将CortexA9和FPGA整合到一起,有兴趣的可以淘宝搜索MiZ702)其中Microblaze是一款软CPU,是软核。ARM9,CortexA9和POWERPC是硬核。这里有两个概念:
1)软核就是用代码就是能现的CPU核,这种核配置灵活,成本较低。但是要占用FPGA宝贵的资源。
2)硬核就是一块电路,做到FPGA内部,方便使用,性能更高。比如Xilinx的DDR内存控制器,就是一种硬核,其运行速度非常高,我们只要做些配置,就可以方便使用。
两种核可谓各有所长。
2.2.5 FPGA还是ASIC
根据具体看情况而定,从我们上面的一些介绍,笔者相信你已经有一定的判断能力了。笔者的建议是,低速场合,实时性要求的低的地方用ASIC,有些功能用ASIC方便的用ASIC,成本低的用ASIC。排除那些可以不用FPGA地方,那么剩余的就要考虑是不是用FPGA来实现更加方便。一般来说,FPGA程序开发相对来说要难度大一些,并且成本要高一些。
讲了这么多的背景知识,我们来看一小段代码:
u32sum(a,b)
{
a=a+1;
b=b+1;
c=a+b;
returnc;
}
modulesum(clk,a,b,c)
{
[email protected](posedgeclk)begin
a=a+1;
b=b+1;
c=a+b;
end
}
同样是实现了求和,但是,C代码需要N多个(很多)CPU周期才得出结果,而用Verilog一个clk周期就计算出来了。
或许现在你还不知道为什么。没关系,下面的内存笔者讲解Verilog语言基础。
2.2.6 Verilog最最基础语法
Verilog和C在外形上有很大相识的地方,有了C基础背景,至少我们对Verilog语言这种新东西,看起来有点似曾相识的感觉。Verilog看起来就并不陌生。
C语言和Verilog的关键词和结构对比:
C语言和Verilog运算符对比:
真是太振奋人心了,一切都是这么熟悉。学好FPGA已经没有心理障碍了。
2.2.7 关键字
信号部分:
input关键词,模块的输入信号,比如input Clk,Clk是外面关键输入的时钟信号;
output关键词,模块的输出信号,比如output[3:0]Led; 这个地方正好是一组输出信号。其中[3:0]表示0~3共4路信号。
这个和计算机语言比如C,C++,BASIC语言等的数组要实现的目的是类似的,只是Verilog中这种中括号[a:b]C,的写法稍微有点不同,并且表示的意义要简单一些,主要是为了简化
描述。Output类型可以是wire,output,inout类型;
inout模块输入输出双向信号。这种类型,我们的例子24LC02中有使用。数总线的通信中,这种信号被广泛应用;
wire关键词,线信号。例如:wire C1_Clk; 其中C1_Clk就是wire类型的信号;
线信号,三态类型,我们一般常用的线信号类型有input,output,inout,wire;
reg关键词,寄存器。和线信号不同,它可以在always中被赋值,经常用于时序逻辑中。比如reg[3:0]Led;表示了一组寄存器。
结构部分:
module()
…
endmodule
代表一个模块,我们的代码写在这个两个关键字中间
[email protected]()括号里面是敏感信号。这里的[email protected](posedge Clk)敏感信号是posedge Clk含义是在上升沿的时候有效,敏感信号还可以negedge Clk含义是下降沿的时候有效,这种形式一般时序逻辑都会用到。还可以是*这个一符号,如果是一个*则表示一直是敏感的,一般用于组合逻辑。
assign用来给output,inout以及wire这些类型进行连线。assign相当于一条连线,将表达式右边的电路直接通过wire(线)连接到左边,左边信号必须是wire型(output和inout属于wire型)。当右边变化了左边立马变化,方便用来描述简单的组合逻辑。示例:
wire a, b, y;
assign y = a & b;
这些语句含义上都和高级语言一样:
if(...)begin
............
End
if(...)begin
............
end
else begin
............
End
if(...)begin
............
end
else if(...)begin
............
end
case(...)
............
endcase
begin..... end作用域范围,类似于C的大括号。用法举例:
[email protected](posedge clk)begin
............
end
符号部分:(我们这里FALSH为0,TRUE为1)
“;”分号用于每一句代码的结束,以表示结束,和C语言一样。
“:”冒号,用在数组,和条件运算符以及case语句结构中。case结构会在后面讲解。
“<=”赋值符号,非阻塞赋值,在一个always模块中,所有语句一起更新。它也可以表示小于等于,具体是什么含义编译环境根据当前编程环境判断,如果“<=”是用在一个if判断里如:if(a <= 10);当然就表示小于等于了。
“=”阻塞赋值,或者给信号赋值,如果在always模块中,这条语句被立刻执行。阻塞赋值和非阻塞赋值将再后面详细举例说明。
看下两段代码
非阻塞赋值
[email protected](posedgeclk)begin//非阻塞赋值
A<=B;
C<=A;
end
执行结果是A的结果是B,C的结果是旧A
阻塞赋值
[email protected](posedgeclk)begin//阻塞赋值
A=B;
C=A;
end
执行结果是A的结果是B,C的结果是B
在上段代码中可以看到,一般情况下,我们使用的是非阻塞赋值,这样可以很好的控制同步性。
“+,-,*,/,% ”是加、减、乘、除运算符号,这些使用和C语言基本是一样的,当你用到这些符号时,编译后会自动生成或者消耗FPGA原有的加法器或是乘法器等。其中符号/,%会消耗大量的逻辑,谨慎使用。
“<”小于,比如A<B含义就是A和B比较,如果A小于B就是TURE,否则为FALSE。
“<=”小于等于,比如A<=B含义就是A和B比较,如果A小于等于B就是TURE,否则为FALSE。
“>”大于,比如A>B含义就是A和B比较,如果A大于B就是TURE,否则为FALSE。
“>=”大于等于,比如A>=B含义就是A和B比较,如果大于等于B就是TURE,否则为FALSE。
“==”等于等于,比如A==B含义就是A和B比较,如果A等于B就是TURE,否则为FALSE。
“!=”不等于,A!=B含义是A和B比较,如果A不等于B就是TURE,否则为FALSE.
“>>”右移运算符,比如A>>2表示把A右移2位。
“<<”左移运算符,比如A<<2表示把A左移2位。
“~”按位取反运算符,比如A=8’b1111_0000;则~A的值为8’b0000_1111;
“&”按位于与,比如A=8’b1111_0000;B=8’b1010_1111;则A&B结果为8’b1010_0000;
“^”异或运算符,比如A=8’b1111_0000;B=8’b1010_1111;则A^B结果为8’b0101_1111;
“&&”逻辑与,比如A==1,B==2;则A&&B结果为TRUE;如果A==1,B==0,则A&&B结果为FALSE,一般用于条件判断。
A = B ? C : D是一个条件运算符,含义是如果B为TRUE则把C连线A,否则把D连线A。B通常是个条件判断,用小括弧括起:
assign C1_Clk = (C1==25‘d24999999) ? 1 : 0 ;
C1_Clk,是一个wire类型的信号,当C1==25‘d24999999时候,连线到1,否则连线到0.
“{}”大括号的作用,在Verilog中,大括号是起到拼接的作用,比如,我看可以把{a,b,c}<={d,e,f};如此来赋值。如果是wire类型的信号,也可以这样{a,b,c}={d,e,f};总之,{}就是起到了拼接的作用,把不同的独立单元拼凑起来。
“{}”在Verilog中表示拼接符,{a,b}这个的含义是将括号内的数按位并在一起,比如:{1001,1110}表示的是10011110。拼接是Verilog相对于其他语言的一大优势,在以后的编程中请慢慢体会。
参数部分:
parameter定义一个符号a为常数(十进制180找个常量的定义等效方式):
parameter a = 180;//十进制,默认分配长度32bit(编译器默认)
parameter a = 8’d180;//十进制
parameter a = 8’haa; //十六进制
parameter a = 8’b1010_1010; //二进制
预处理命令
//--------------------------------
`include file1.v
//--------------------------------
`define X = 1;
//--------------------------------
`deine Y;
`ifdef Y
Z=1;
`else
Z=0;
`endif
//--------------------------------
有的时候我们一些公共的宏参数,我们可以放在一个文件中,比如这个文件名字为xx.v那么我们可以`include xx.v就可以包含找个文件中定义的一些宏参数。我还是来详细说明下吧!
话说Verilog 的`include和C语言的include用法是一样一样的,要说区别可能就在于那个点吧。
include一般就是包含一个文件,对于Verilog这个文件里的内容无非是一些参数定义,所以这里再提几个关键字:`ifdef `define `endif(他们都带个点,呵呵)。
他们联合起来使用,确实能让你的程序多样化,就拿VGA程序说事吧。
首先,你可以新建一个.h文件(可以直接新建一个TXT,让后将后缀换成.h)其实这个后缀没所谓,.v也是可以的,我觉得,写成.h更能体现出这个文件的意义。
假设有个lcd_para.h文件,内容如下:
// 640 * 480
`ifdef VGA_640_480_60FPS_25MHz
`define H_FRONT 11‘d16
`define H_SYNC 11‘d96
`define H_BACK 11‘d48
`define H_DISP 11‘d640
`define H_TOTAL 11‘d800
`define V_FRONT 11‘d10
`define V_SYNC 11‘d2
`define V_BACK 11‘d33
`define V_DISP 11‘d480
`define V_TOTAL 11‘d525
`endif
// 800 * 600
`ifdef VGA_800_600_72FPS_50MHz
`define H_FRONT 11‘d56
`define H_SYNC 11‘d120
`define H_BACK 11‘d64
`define H_DISP 11‘d800
`define H_TOTAL 11‘d1040
`define V_FRONT 11‘d37
`define V_SYNC 11‘d6
`define V_BACK 11‘d23
`define V_DISP 11‘d600
`define V_TOTAL 11‘d666
`endif
//---------------------------------
`define H_Start (`H_SYNC + `H_BACK)
`define H_END (`H_SYNC + `H_BACK + `H_DISP)
`define V_Start (`V_SYNC + `V_BACK)
`define V_END (`V_SYNC + `V_BACK + `V_DISP)
这里为VGA定义了两种分辨率,通过`define VGA_800_600_60MHz或 VGA_640_480_60FPS_25MHz 或`define VGA_800_600_72FPS_50MHz来决定使用哪种分辨率。
比如,我的xxx.v文件想调用lcd_para.h,那么xxx.v我可以写到:
`define VGA_800_600_60MHz //这句要放在"lcd_para.h"的上面,不然编译不通过
`include "lcd_para.h"
那么xxx.v文件中就可以用lcd_para.h中的参数了,且对应是VGA_800_600_60MHz下的参数。
其次`include "lcd_para.h" 这个路径也有一点讲究,xxx.v作为引用lcd_para.h的文件它和lcd_para.h在同一文件夹下才能怎么写,就是相对路径一说了。也就是以xxx.v为当前路径去引索lcd_para.h文件的位子。所以如果他们不再一个文件夹那么请写出更详细(正确)的路径。顺便说一句,lcd_para.h添不添加到工程是无所谓的,只要路径
对了即可,当然我还是建议添加到工程,且和.v文件放在同一文件夹下,以方便观察和管理。
2.2.8 Verilog中数值表示的方式
如果我们要表示一个十进制是180的数值,在Verilog中的表示方法如下:
二进制:8’b1010_1010; //其中“_”是为了容易观察位数,可有可无。
十进制:8’d180;
16进制:8’hAA;
讲到这里,笔者以最快的速度,最简单的方式,让读者学习了Verilog语言的语法部分。具备这些基础知识,下面笔者将带你通过代码来学习Veriog语言。最后,笔者提一点建议,学习Verilog多看别人写的优秀的代码,多看官方提供的代码和文档。其中官方提供的代码,很多时候代表了最新的用法,或者推荐的用法。读者学习,首先把最最基础的掌握好,这样,在项目中遇到了问题,也能快速学习,快速解决。
对于理论知识的学习,没必要一开始就研究得那么深刻,只是搞理论学习,对于学习Verilog语言,或者FPGA开发是不实际的,要联系理论和实践结合。多仿真,多验证,多问题,多学习,多改进。