一、verilog语法基础
1、硬件描述语言
1.1 什么是硬件描述语言
硬件描述语言(Hardware Description Language, HDL)是电子系统硬件行为描述、结构描述、数据流描述的语言。利用这种语言,数字电路系统的设计可以从顶层到底层(从抽象到具体)逐层描述自己的设计思想,用一系列分层次的模块来表示极其复杂的数字系统。然后,利用电子设计自动化(Electronics Design Automation, EDA )工具,逐层进行仿真验证,再把其中需要变为实际电路的模块组合,经过自动综合工具转换到门级电路网表。接下去,再用专用集成电路(Application Specific Integrated Circuit,ASIC)或现场可编程门阵列 FPGA(Field-Programmable Gate Array,FPGA)自动布局布线工具,把网表转换为要实现的具体电路布线结构,VHDL(Very High Speed Integration Circuit,HDL)和 Verilog HDL 语言适应了这种趋势的要求,先后成为 IEEE 标准。
1.2 主要的硬件描述语言
硬件描述语言主要包括:Verilog、VHDL、SystemVerilog。
1.2.1 Verilog HDL
Verilog HDL拥有广泛的设计群体,成熟的资源也比 VHDL 丰富,语言从C编程语言中继承了多种操作符和结构,对于熟悉C语言的同学,学习verilog时上手会很快。
1.2.2 VHDL
超高速集成电路硬件描述语言(Very High Speed Integrated Circuit Hardware Description Language,VHDL)是一种标准化程度较高的硬件描述语言,其语言特点包括:语法严谨、结构规范、移植性强、数据类型丰富。除此之外VHDL支持层次结构设计,独立于器件和设计平台,程序复用性强。
1.2.3 SystemVerilog
SystemVerilog结合了来自 Verilog、VHDL、C++的概念,将硬件描述语言与现代的高层级验证语言结合了起来。所以SystemVerilog有上述两种语言和计算机高级语言的特征。
建议学习Verilog HDL的原因:
Verilog HDL 推出已经有 20 年了,拥有广泛的设计群体,成熟的资源也比 VHDL 丰富,Verilog HDL未来发展趋势。
它非常容易掌握,只要有 C 语言的编程基础,通过比较短的时间,经过一些实际的操作,可以在 2 ~ 3 个月内掌握这种设计技术。
在中国很多集成电路设计公司都采用Verilog,一般大型项目采用VHDL。
SystemVerilog是Verilog的升级,对于“小白”,应该从学习Verilog开始。
2、基础知识
2.1 逻辑值
逻辑0:表示低电平,也就对应我们的电路GND。
逻辑1:表示高电平,也就对应我们电路的VCC。
逻辑X:表示未知,有可能是高电平,也有可能是低电平。
逻辑Z:·表示高阻态,外部没有激励信号(输入信号),是一个悬空状态(未接高低电平)。
2.2 进制格式
Verilog数字进制格式包括二进制(binary)、八进制(octal)、十进制(decimal)和十六进制(hexadecimal)。一般常用的为二进制、十进制和十六进制。
二进制表示如下:4’b0101表示4位二进制数字0101
八进制表示如下:4’o7表示4位八进制数7(二进制0111)
十进制表示如下:4’d2表示4位十进制数字2(二进制0010)
十六进制表示如下:4’ha表示4位十六进制数字a(二进制1010)
注意:如果数值未表明位宽和进制,则:默认为32位宽的十进制数字(32’d),常见写法:=16’b1001_1010_1010_1001=16’h9AA9
2.3 标识符(变量名)
标识符:用于定义模块名、端口名、信号名等(就是起个名字而已)。标识符命名规则:
标识符可以是任意一组字母、数字、$和_(下划线)符号的组合。
但标识符不允许以数字开头,和c语言类似。
标识符严格区分大小写,大小写敏感。
不建议大小写混合使用。
普通内部信号建议全部小写。
命名最好体现信号的含义,简介、清晰、易懂(见名知意)
例子:
有意义的标识符:sum
用下划线区分词:cpu_addr
采用一些前缀或后缀:时钟clk_50或sys_clk
3、数据类型
3.1 概述
在Verilog中,主要数据类型:
- 寄存器数据类型(register)
- 线网数据类型(wire)
- 参数数据类型(parameter)
- 整型(integer)
注意:真正在数字电路中起作用的数据类型是寄存器数据类型和线网数据类型。
3.2 寄存器类型
寄存器表示一个抽象的数据存储单元,通过赋值语句可以改变寄存器存储的值 ,寄存器数据类型的关键字是reg,reg类型数据的默认初始值为不定值x。
如: reg [9:0] delay_cnt; //双斜杠表示注释,不参与编译
reg key_reg;
reg类型的数据只能在always语句和initial语句中被赋值。如果该过程语句描述的是时序逻辑,即always语句带有时序时钟信号,则该寄存器变为对应为触发器;如果该过程语句描述的是组合逻辑,即always语句不带有时钟信号,则该寄存器变为对应硬件连线。
3.3 线网类型
线网数据类型表示结构实体(例如门)之间的物理连线。
线网类型的变量不能存储值,它的值是由驱动它的元件所决定。
驱动线网型变量的元件有逻辑门、连续赋值语句(assign)等。
如果没有驱动元件连接到线网类型的变量上,则该变量就是高阻的,即为Z。
线网数据类型包括wire类型和tri类型,其中最常用的就是wire类型。
3.4 参数类型
参数其实就是一个常量,在Verilog HDL中用parameter定义常量。我们可以一次定义多个参数,参数与参数之间需要用逗号(,)隔开。每个参数定义的右边必须是一个常数表达式。
如: parameter H_FRONT = 11’d11; //行显示前沿
parameter H_BACK = 11’d2; //行显示后沿
参数型数据常用于定义状态机的状态、数据位宽和延迟大小等。采用标识符来表示一个常量可以提高程序的可读性和可维护性。在模块调用时,可通过参数传递来改变被调用模块中已定义的参数。
4、运算符
Verilog中的操作符按照功能分为以下几种类型:
算数运算符
关系运算符
逻辑运算符
条件运算符
位运算符
移位运算符
拼接运算符
4.1 算数运算符
注:除法运算(/):整数与整数相除的结果为整数
4.2 关系运算符
4.3 逻辑运算符
&&:左右两边同时为1,则结果为1
||:左右两边同时为0,则结果为0
4.4 条件运算符(三目运算符)
4.5 位运算符(二进制运算)
~(取反):0变1,1变0
&(位与):同时为1,则结果为1
|(位或):有1,则1
^(异或):相同为0不同为1
注意:如果位运算左右两边变量的位宽不同,则比较小的位宽高位补0。
4.6 移位运算符
注意:两种移位运算符都用0来填补移出的空位。左移时,位宽增加;右移时,位宽不变。
如:4’b1001 << 2 = 6’b100100;
4’b1001 >> 1 = 4’b0100;
4.7 拼接符合
5、总结
本次学习了verilog语法基础,为之后进行verilog编程打下了基础。
二、FPGA点亮led
1、建立项目
1.1 目录结构
1.2 新建项目
1.3 项目命名
1.4
之后一直按next直到选择芯片
1.5 EDA设置
二、编写verilog程序
2.1 新建verilog文件
2.2 完整代码
module led(
input clk,//开发板晶振频率50MHz
input rst_n,//复位下绛沿有效
output reg ld //led灯
);
reg [25:0] cnt; //时钟寄存器
//计时器模块
always@(negedge rst_n or posedge clk)begin
if(!rst_n)begin
cnt <= 26'd0;//初始化计时器为0
ld <= 1'b1;//初始化led灯,高电平有效
end
else if(cnt == 26'd50_000_000-1)begin
cnt <= 26'd0;
ld <= ~ld;//1s钟led取反
end
else begin
cnt <= cnt + 26'd1;
ld <= ld;//其他时刻,led等于其自身
end
end
endmodule
2.3 保存文件
ctrl+s选择保存到src目录
2.4 分析与综合
2.5 引脚布线
注意:布局布线之前一定要分析综合
2.6 全编译
三、pwm呼吸灯
1、系统设计
使用开发板上的四个led灯实现1s间隔的呼吸灯。
2、设计输入
创建pwm_led灯,编写verilog代码
3、代码
module pwm_led(
input clk ,
input rst_n ,
output reg [3:0] led
);
parameter CNT_US = 6'd49;//50x20=1000ns=1us
parameter CNT_MS = 10'd999;//1usx1000=1ms
parameter CNT_S = 10'd999;//1msx1000=1s
reg [5:0] cnt_us;
wire add_cnt_us;
wire end_cnt_us;
reg [9:0] cnt_ms;
wire add_cnt_ms;
wire end_cnt_ms;
reg [9:0] cnt_s;
wire add_cnt_s;
wire end_cnt_s;
reg flag;//闪烁标志
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_us <= 0;
end
else if(add_cnt_us)begin
if(end_cnt_us)begin
cnt_us <= 0;
end
else begin
cnt_us <= cnt_us + 1;
end
end
else begin
cnt_us <= cnt_us;
end
end
assign add_cnt_us = 1'd1;
assign end_cnt_us = add_cnt_us && cnt_us == CNT_US;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_ms <= 0;
end
else if(add_cnt_ms)begin
if(end_cnt_ms)begin
cnt_ms <= 0;
end
else begin
cnt_ms <= cnt_ms + 1;
end
end
else begin
cnt_ms <= cnt_ms;
end
end
assign add_cnt_ms = end_cnt_us;
assign end_cnt_ms = add_cnt_ms && cnt_ms == CNT_MS;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_s <= 0;
end
else if(add_cnt_s)begin
if(end_cnt_s)begin
cnt_s <= 0;
end
else begin
cnt_s <= cnt_s + 1;
end
end
else begin
cnt_s <= cnt_s;
end
end
assign add_cnt_s = end_cnt_ms;
assign end_cnt_s = add_cnt_s && cnt_s == CNT_S;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag <= 1'b0;
end
else if(end_cnt_s)begin
flag <= ~flag;//1s取反
end
else begin
flag <= flag;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
led <= 4'b0;
end
else begin
if(flag)begin//亮pwm
led <= {cnt_s > cnt_ms, cnt_s > cnt_ms,cnt_s > cnt_ms,cnt_s > cnt_ms};
end
else begin//灭pwm
led <= {cnt_s < cnt_ms, cnt_s < cnt_ms,cnt_s < cnt_ms,cnt_s < cnt_ms};
end
end
end
endmodule
四、数码管静态显示
1、模块代码
1.1 time_count模块。
module time_count(
input clk ,//50MHz时钟信号
input rst_n,//复位信号
output reg flag//一个时钟周期的脉冲信号
);
parameter MAX_NUM = 25'd25_000_000;//计数器最大计数值
reg [24:0] cnt ; //时钟分频计数器
//计数器对时钟计数,每0.5s,输出一个时钟周期脉冲信号
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin//按复位时
flag <= 1'b0;//信号为0
cnt <= 25'd0;//计数器清零
end
else if(cnt < MAX_NUM - 1'b1)begin//如果没到时间
flag <= 1'b0;//信号为0
cnt <= cnt + 1'b1;//计数器正常累计+1
end
else begin //否则到时间
flag <= 1'b1;//信号变为1
cnt <= 25'd0;
end
end
endmodule
1.2 seg_led_static模块
module seg_led_static(
input clk ,
input rst_n ,
input flag ,
output reg [5:0] sel ,//数码管位选信号
output reg [7:0] seg //数码管段选信号
);
reg [3:0] num;//数码管显示十六进制数
//控制数码管位选信号(注:低电平有效),选中所有的数码管
always @(posedge clk or negedge rst_n)begin
if(!rst_n)//如果按复位键0
sel <= 6'b111111;//则默认为高电平
else
sel <= 6'b000000;//否则为低电平
end
//每次通知信号flag到达时,数码管计数加1
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
num <= 4'h0;
else if(flag)begin
if(num < 4'hf)
num <= num + 1'h1;
else
num <= 4'h0;
end
else begin
num <= num;
end
end
//根据数码管显示的数值,控制段选信号
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
seg <= 8'b0;
else begin
case(num)//匹配16进制数
4'h0: seg <= 8'b1100_0000;//匹配到后参考共阳极真值表
4'h1: seg <= 8'b1111_1001;
4'h2: seg <= 8'b1010_0100;
4'h3: seg <= 8'b1011_0000;
4'h4: seg <= 8'b1001_1001;
4'h5: seg <= 8'b1001_0010;
4'h6: seg <= 8'b1000_0010;
4'h7: seg <= 8'b1111_1000;
4'h8: seg <= 8'b1000_0000;
4'h9: seg <= 8'b1001_0000;
4'ha: seg <= 8'b1000_1000;
4'hb: seg <= 8'b1000_0011;
4'hc: seg <= 8'b1100_0110;
4'hd: seg <= 8'b1010_0001;
4'he: seg <= 8'b1000_0110;
4'hf: seg <= 8'b1000_1110;
default : seg <= 8'b1100_0000;
endcase
end
end
endmodule
1.3top_seg_led_static模块
module top_seg_led_static(
input clk ,//50MHz系统时钟
input rst_n,//系统复位信号(低有效)
output [5:0] sel ,//数码管位选
output [7:0] seg//数码管段选
);
parameter MAX_NUM = 25'd25_000_000;// 数码管变化的时间间隔0.5s
wire add_flag ;// 数码管变化的通知信号
//每隔0.5s产生一个时钟周期的脉冲信号
time_count #(.MAX_NUM(MAX_NUM)) u_time_count(
.clk (clk) ,//50MHz时钟信号
.rst_n (rst_n),//复位信号
.flag (add_flag)//一个时钟周期的脉冲信号
);
//每当脉冲信号到达时,使数码管显示的数值加1
seg_led_static u_seg_led_static(
.clk (clk) ,
.rst_n (rst_n) ,
.flag (add_flag),
.sel (sel) ,
.seg (seg)
);
endmodule
2.仿真
`timescale 1ns/1ns
module top_seg_led_static_tb();
reg clk ;
reg rst_n ;
wire [5:0] sel ;
wire [7:0] seg ;
parameter CYCLE = 5'd20;//周期20ns
parameter MAX_NUM = 8'd100;//调小间隔时间100*20ns
always #(CYCLE/2) clk = ~clk;//翻转时钟
initial begin
clk = 0 ;//时钟初始为0
rst_n = 0 ;//复位初始为0
#(CYCLE) ;//延迟20ns
rst_n = 1 ;//复位置1
#(16*MAX_NUM*CYCLE);//显示0-f时间
$stop ;//停止
end
top_seg_led_static#(.MAX_NUM (MAX_NUM)) u_top_seg_led_static(
.clk (clk) ,//50MHz系统时钟
.rst_n (rst_n),//系统复位信号(低有效)
.sel (sel) ,//数码管位选
.seg (seg) //数码管段选
);
endmodule
四、总结
通过这次实习,我深入了解了FPGA方向的相关知识和技术,如verilog语法基础,按键控制led灯,呼吸灯,数码管显示控制,写仿真文件等,并通过实践进一步提高了自己的技能水平。这段实习经历让我对物联网领域有了更深入的认识,也为我的未来发展打下了坚实的基础。
五、参考链接
https://blog.youkuaiyun.com/weixin_43828944/article/details/122296149
https://blog.youkuaiyun.com/weixin_43828944/article/details/121609480
https://blog.youkuaiyun.com/weixin_43828944/article/details/122099141