【FPGA快速入门1】Verilog简介、基础语法、程序框架、结构语句、赋值语句以及条件语句的学习总结
1. FPGA介绍、Verilog简介以及Verilog基础语法
1.1 FPGA介绍
FPGA是一种可通过编程来修改其逻辑功能的数字集成电路(芯片)
常用的可编程逻辑器件包括两种:复杂可编程逻辑器件CPLD(Complex Programmable Logic Device)和现场可编程门阵列FPGA(Field Programmable Gate Array)。两者的本质差异在于电路结构的不同,CPLD它是基于“乘积项”的与或逻辑阵列,而FPGA是基于“查找表”的CLB阵列。
1.2 Verilog简介
Verilog HDL 和 VHDL都是常用的硬件描述语言,这两种语言都有用到,VHDL在很多欧洲国家用的比较多,而美国和中国都以Verilog HDL为主,实际上Verilog HDL相对于VHDL灵活性更好,性能也比较好,也容易学,目前市面上的学习开发板像野火、正点原子学习平台等都用的是Verilog HDL的例程。
Verilog语言最初是于1983年由Gateway Design Automation 公司为其模拟器产品开发的硬件建模语言。后经过改进得到Verilog-2001版本。
Verilog和C的区别:
Verilog是硬件描述语言,在编译下载到FPGA之后,会生成电路,它是并行运行的。
C语言是软件编程语言,编译下载到单片机之后,是存储器中的一组指令,而单片机处理软件指令需要取指、译码、执行,这个过程是串行执行的。
Verilog和C的区别也是FPGA和单片机/CPU的区别。FPGA由于全并行处理,处理速度非常快,这个是FPGA的最大的优势,这一点是单片机/CPU替代不了的。
1.3 Verilog基础语法
逻辑值:
逻辑0:表示低电平
逻辑1:表示高电平
逻辑X:表示未知,有可能是高电平,也有可能使低电平
逻辑z:表示高阻态,外部没有激励信号,是一个悬空状态
数学进制格式:
Verilog数字进制格式包括二进制、八进制、十进制和十六进制。
二进制表示如下:4‘b0101表示4位二进制数学0101
十进制表示如下:4’d2表示4位十进制数字2(二进制0010)
十六进制表示如下:4‘ha表示4位十六进制数字a(二进制1010)
如果没有位宽4,则默认是32位的位宽、
有时候为了增加程序的可读性,在每四位之间加一个下划线:16’b1001_1010_1010_1001=16’h9AA9
标识符:用于定义模块名、端口名、信号名等
可以用任意一组字母、数字、$符号和_(下划线)符号的组合;但标识符的第一个字符必须是字母或者下划线;标识符是区分大小写的;
数据类型:真正在数字电路中起作用的数据类型是寄存器数据类型和线网数据类型
寄存器数据类型:表示一个抽象的数据存储单元,通过赋值语句可以改变寄存器存储的值,关键字是reg,reg类型数据的默认初始值为不定值x
//reg define
reg[31:0] delay_cnt; //延时计数 32位寄存器 从高位开始写
reg key_reg;
reg类型的数据只能在always语句和initial语句中被赋值。
如果该过程语句描述的是时序逻辑,即always语句带有时钟信号,则该寄存器变量对应为触发器;如果该过程语句描述的是组合逻辑,即always语句不带有时钟信号,则该寄存器变量对应为硬件连线。
线网数据类型:表示结构实体(例如门)之间的物理连线,线网类型的变量不能存储值,它的值是由驱动它的元件所决定。驱动线网类型的变量的元件有门、连续赋值语句、assign等。如果没有驱动元件连接到线网类型的变量上,则该变量就是高阻的,即其值为z。
线网数据类型包括wire型和tri型,其中最常用的就是wire类型。
// wire define
wire key_flag; //默认一位线网类型
参数数据类型:就是一个常量,在Verilog HDL中用parameter定义常量。可以一次定义多个参数,参数与参数之间需要用逗号隔开。每个参数定义的右边必须是一个常数表达式。
//parameter define 4.3'RGB LCD
parameter H_SYNC = 11'd41; //行同步
parameter H_BACK = 11'd2; //行显示后沿
parameter H_DISP = 11'd480; //行有效数据
parameter H_FRONt = 11'd525; //行扫描周期
运算符:算术运算符、关系运算符、逻辑运算符、条件运算符、位运算符、移位运算符、拼接运算符
算术运算符:
符号 | 使用方法 | 说明 |
---|---|---|
+ | a+b | a加上b |
- | a-b | a减去b |
* | a*b | a乘以b |
/ | a/b | a除以b |
% | a%b | a模除b |
关系运算符:
符号 | 使用方法 | 说明 |
---|---|---|
> | a>b | a大于b |
< | a<b | a小于b |
<= | a<=b | a小于等于b |
>= | a>=b | a大于等于b |
== | a==b | a等于b |
!= | a!=b | a不等于b |
逻辑运算符:
条件操作符:
符号 | 使用方法 | 说明 |
---|---|---|
?: | a?b:c | 如果a为真,就选择b,否则选择c |
位运算符:
移位运算符:
两种移位运算符都用0来填补移出的空位。左移时,位宽增加;右移时,位宽不变。
4‘b1001<<2=6’b100100;
4’b1001>>1=b’b0100;
位拼接运算符:
符号 | 使用方法 | 说明 |
---|---|---|
{} | {a,b} | 将a和b拼接起来,作为一个新信号 |
c={a,b[3:0]};
2.FPGA用到的程序框架
2.1 Verilog 注释
Verilog中有两种注释方式:一种以//开头的语句,它表示以//开始到本行结束都属于注释语句。
// wire defiine
wire locked; //PLL输出有效标志
wire sys_rst_n; //系统复位信号
wire error_flag; //读写测试错误标志
另一种是以“*/”结束,在两个符号之间的语句都是注释语句,因此可扩展到多行。
//例如PLL,产生各模块所需要的时钟
/*
pll_clk u_pll_clk(
.inclk0 (clk),
.areset (~rst_n),
.c0 (clk_50m),
.c1 (clk_100m),
.c2 (clk_100m_shift),
.locked (locked)
);
*/
2.2 Verilog 程序框架
Verilog 的基本设计单元是“模块”(block),一个模块有描述接口和描述逻辑功能两部分组成。
module block(a,b,c,d);
input a,b;
outpur c,d;
assign c=a|b;
assign d=a&b;
endmodule
每个Verilog程序包括4个部分:端口定义、IO说明、内部信号申明、功能定义。
功能定义部分有三种方法:
1.assign语句 描述组合逻辑
2.always语句 描述组合/时序逻辑
3.例化实例元件 如:and #2 u1(q,a,b);
以上三种逻辑功能都是并行的
需要注意的是:在always块中,逻辑是顺序执行的。而多个always块之间是并行的。
2.3 模块的调用
在模块调用时,信号通过模块端口在模块之间传递。
module seg_led_static_top(
input sys_clk, //系统时钟
input sys_rst_n, //系统复位信号(低有效)
output [5:0] sel, //数码管位选
output [7:0] seg_led //数码管段选
);
//parameter define
parameter TIME_SHOW = 25'd25000_000; //数码管变化的时间间隔0.5s
//wire define
wire add_flag; //数码管变化的通知信号
module time_count(
input clk,
input rst_n,
output reg flag
);
//parameter define
parameter MAX_NUM = 50000_000; //数码管变化的时间间隔0.5s
//reg define
reg [24:0] cnt;
//**main code
//每隔0.5s产生一个时钟周期的脉冲信号
time_count #(
.MAX_NUM (TIME_SHOW)
) u_time_count(
.clk (sys_clk),
.rst_n (sys_rst_n),
.flag (add_flag)
);
另一种端口连接方式(需要一一对应):
//每隔0.5s产生一个时钟周期的脉冲信号
time_count #(
.MAX_NUM (TIME_SHOW)
) u_time_count(
sys_clk,
sys_rst_n,
add_flag
);
//每当脉冲信号到达时,使数码管显示的数值加1
seg_led_static u seg_led_static (
.clk (sys_clk),
.rst_n (sys_rst_n),
.add_flag (add_flag),
.sel (sel),
.seg_led (seg_led)
);
endmodule
3.Verilog语句中的高级知识点(三种语句)
3.1 结构语句
Verilog里面有两种结构语句:initial和always。initial语句它在模块中只执行一次。常用于测试文件的编写,用来产生仿真测试信号(激励信号),或者用于对存储器变量赋初值。
always语句一直在不断地重复活动。但是只有和一定的时间控制结合在一起才有作用。always的时间控制可以是沿触发,也可以是电平触发;可以是单个信号,也可以是多个信号,多个信号中间要用关键字or链接。always语句后紧跟的过程块是否运行,要看它的触发条件是否满足。
//计数器对系统时钟计数,计时0.2秒
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
counter <= 24'd0;
else if (counter <= 24'd1000_0000)
counter <=counter + 1'b1;
else
counter <= 24'd0;
end
沿触发的always块常常描述时序逻辑行为。由关键词or连接的多个事件名或信号名组成的列表称为“敏感列表”。
电平触发的always块常常描述组合逻辑行为:
always @(a or b or c or d or e or f or q or h or p or m) begin
out1 = a?(b+c):(d+e);
out2 = f?(g+h):(p+m);
end
如果组合逻辑块语句的输入变量很多,那么编写敏感列表会很繁琐,可以用*来代替敏感列表中的变量
always @(*) begin
out1 = a?(b+c):(d+e);
out2 = f?(g+h):(p+m);
end
@(*)表示对后面语句块中所有输入变量的变化都是敏感的。
3.2 赋值语句
Verilog HDL语言中,信号有两种赋值方式:
1.阻塞赋值,如a=b
阻塞赋值可以认为只有一个步骤的操作:即计算RHS并更新LHS。阻塞的概念是指:在同一个always块中,后面的赋值语句是在前一句赋值语句结束后才开始赋值的。
2.非阻塞赋值,如a<=b
非阻塞赋值可以认为两个步骤:(1).赋值开始的时候,计算RHS;(2).赋值结束的时候,更新LHS。非阻塞的概念是指:在计算非阻塞赋值的RHS以及更新LHS期间,允许其他的非阻塞赋值语句同时计算RHS和更新LHS。非阻塞赋值只能用于对寄存器类型的变量进行赋值,因此只能用在initial和always块中。
需要注意的是:在同一个always块中不要即用非阻塞赋值又用阻塞赋值。
3.3 条件语句
if_else 语句有三种写法:
(1)if(a>b)
out=data_1;
(2)if(a>b)
out=data_1;
else
out=data_2;
(3)if(表达式1)
语句1;
else if(表达式2)
语句2;
else if(表达式3)
语句3;
else
语句4;
需要注意的是:1.允许一定形式的简写,如:if(a) 等同于 if (a==1)
if (!a) 等同于 if(a!=1)
2.if 语句对表达式的值进行判断,若为0,x,z,则按假处理;若为1,按真处理
3.if 和 else 后面的操作语句可以用 begin 和 end 包含多个语句
4.允许 if 语句的嵌套。
case 语句(多分支选择语句)
1.分支表达式的值互不相同;
2.所有表达式的位宽必须相等;不能用‘bx来代替n’bx
3.casez 比较时,不考虑表达式中的高阻值
4.casez 不考虑高阻值z 和不定值 x
reg [7:0] sel;
casez(sel)
8'b1100_zzzz: 语句1;
8’b1100_xxzz: 语句2;
endcase