目录
Verilog HDL( Hardware Description Language )是在用途最广泛的 C 语言的基础上发展起来的一种硬件描述语言,具有灵活性高、易学易用等特点。Verilog HDL 可以在较短的时间内学习和掌握,目前已经在 FPGA 开发 /IC 设计领域占据绝对的领导地位。
1 Verilog 概述
1.1 Verilog 简介
1.2 为什么需要 Verilog
1.3 Verilog 和 VHDL 区别
1.4 Verilog 和 C 的区别
2 Verilog 基础知识
2.1 Verilog 的逻辑值

2.2 Verilog 的标识符
CountCOUNT // 与 Count 不同。R56_68FIVE$
countfifo_wr
2.3 Verilog 的数字进制格式
2.4 Verilog 的数据类型
2.4.1 寄存器类型
//reg definereg [ 31 : 0 ] delay_cnt ; //延时计数器reg key_flag ; //按键标志
2.4.2 线网类型
//wire definewire data_en ; //数据使能信号wire [ 7 : 0 ] data ; //数据
2.4.3 参数类型
//parameter defineparameter DATA_WIDTH = 8 ; //数据位宽为8位
2.5 Verilog 的运算符

2.5.2 关系运算符

2.5.3 逻辑运算符

2.5.4 条件运算符
2.5.5 位运算符

位运算符的与、或、非与逻辑运算符逻辑与、逻辑或、逻辑非使用时候容易混淆,逻辑运算符一般用在条件判断上,位运算符一般用在信号赋值上。
2.5.6 移位运算符

假设 a 有 8bit 数据位宽,那么 a<<2,表示 a 左移 2bit,a 还是 8bit 数据位宽,a 的最高 2bit 数据被移位丢弃了,最低 2bit 数据固定补 0。如果 a 是 3(二进制:00000011),那么 3 左移 2bit,3<<2,就是 12(二进制:00001100)。一般使用左移位运算代替乘法,右移位运算代替除法,但是这种也只能表示 2 的指数次幂的乘除法。

3 Verilog 程序框架
3.1 注释
/* statement1 ,statement2 ,......statementn */
//statement1
3.2 关键字

虽然上表列了很多,但是实际经常使用的不是很多,实际经常使用的主要如下表所示。

module led(//模块定义以 module 开始,endmodule 结束
input sys_clk , //系统时钟
input sys_rst_n, //系统复位,低电平有效
output reg [3:0] led //4位LED灯
);
//parameter define
parameter WIDTH = 25 ;
parameter COUNT_MAX = 25_000_000; //板载50M时钟=20ns,0.5s/20ns=25000000,需要25bit
//位宽
//reg define
reg [WIDTH-1:0] counter ;
reg [1:0] led_ctrl_cnt;
//wire define
wire counter_en ;
//***********************************************************************************
//** main code
//***********************************************************************************
//计数到最大值时产生高电平使能信号
assign counter_en = (counter == (COUNT_MAX - 1'b1)) ? 1'b1 : 1'b0;
//用于产生0.5秒使能信号的计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0)
counter <= 1'b0;
else if (counter_en)
counter <= 1'b0;
else
counter <= counter + 1'b1;
end
//led流水控制计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0)
led_ctrl_cnt <= 2'b0;
else if (counter_en)
led_ctrl_cnt <= led_ctrl_cnt + 2'b1;
end
//通过控制IO口的高低电平实现发光二极管的亮灭
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0)
led <= 4'b0;
else begin
case (led_ctrl_cnt)
2'd0 : led <= 4'b0001;
2'd1 : led <= 4'b0010;
2'd2 : led <= 4'b0100;
2'd3 : led <= 4'b1000;
default : ;
endcase
end
end
endmodule//模块定义以 module 开始,endmodule 结束
2 到 5 行为端口定义,需要定义 led 模块的输入信号和输出信号,此处输入信号为系统时钟和复位信号,输出为 led 控制信号。
if (en == 1'b1 )a <= 1'b1 ;
if (en == 1'b1 ) begina <= 1'b1 ;end
if (en == 1'b1 ) beginb <= 1'b1 ;c <= 1'b1 ;end
4 Verilog 高级知识点
4.1 阻塞赋值(Blocking)

4.2 非阻塞赋值(Non-Blocking)
代码中使用的是非阻塞赋值语句,从波形图中可以看到,在复位的时候(rst_n=0),a=1,b=2, c=3;而结束复位之后(波形图中的 0 时刻),当 clk 的上升沿到来时(波形图中的 2 时刻),a=0,b=1,c=2。这是因为非阻塞赋值在计算 RHS 和更新 LHS 期间,允许其它的非阻塞赋值语句同时计算 RHS 和更新 LHS。在波形图中的 2 时刻,RHS 的表达是 0、a、b,分别等于 0、1、2,这三条语句是同时更新LHS,所以 a、b、c 的值分别等于 0、1、2。
什么时候使用阻塞赋值,什么时候使用非阻塞赋值:在描述组合逻辑电路的时候,使用阻塞赋值,比如 assign 赋值语句和不带时钟的 always 赋值语句,这种电路结构只与输入电平的变化有关系,代码如下:
assign data = (data_en == 1'b1 ) ? 8'd255 : 8'd0 ;
always @( * ) beginif (en) begina = a0 ;b = b0 ;endelse begina = a1 ;b = b1 ;endend
always @( posedge sys_clk or negedge sys_rst_n ) beginif (! sys_rst_n ) begina <= 1'b0 ;b <= 1'b0 ;endelse begina <= c ;b <= d ;endend
4.3 assign 和 always 区别
assign counter_en = ( counter == ( COUNT_MAX - 1'b1 )) ? 1'b1 : 1'b0 ;always @( * ) begincase ( led_ctrl_cnt )2'd0 : led = 4'b0001 ;2'd1 : led = 4'b0010 ;2'd2 : led = 4'b0100 ;2'd3 : led = 4'b1000 ;default : led = 4'b0000 ;endcaseend
4.4 带时钟和不带时钟的 always
reg [ 3 : 0 ] led;always @(*) begincase ( led_ctrl_cnt )2'd0 : led = 4'b0001 ;2'd1 : led = 4'b0010 ;2'd2 : led = 4'b0100 ;2'd3 : led = 4'b1000 ;default : led = 4'b0000 ;endcaseend
//用于产生 0.5 秒使能信号的计数器always @( posedge sys_clk or negedge sys_rst_n ) beginif ( sys_rst_n == 1'b0 )counter <= 1'b0 ;else if ( counter_en )counter <= 1'b0 ;elsecounter <= counter + 1'b1 ;end
4.5 什么是 latch


4.6 状态机
4.6.1 Mealy 状态机

4.6.2 Moore 状态机

4.6.3 三段式状态机

状态跳转图画完之后,接下来通过 parameter 来定义各个不同状态的参数,如下代码所示:
parameter S0 = 7'b0000001 ; //独热码定义方式parameter S1 = 7'b0000010 ;parameter S2 = 7'b0000100 ;parameter S3 = 7'b0001000 ;parameter S4 = 7'b0010000 ;parameter S5 = 7'b0100000 ;parameter S6 = 7'b1000000 ;
reg [ 6 : 0 ] curr_st ; //当前状态reg [ 6 : 0 ] next_st ; //下一个状态
module divider7_fsm (
//系统时钟与复位
input sys_clk ,
input sys_rst_n ,
//输出时钟
output reg clk_divide_7
);
//parameter define
parameter S0 = 7'b0000001; //独热码定义方式
parameter S1 = 7'b0000010;
parameter S2 = 7'b0000100;
parameter S3 = 7'b0001000;
parameter S4 = 7'b0010000;
parameter S5 = 7'b0100000;
parameter S6 = 7'b1000000;
//reg define
reg [6:0] curr_st ; //当前状态
reg [6:0] next_st ; //下一个状态
//*****************************************************
//** main code
//*****************************************************
//状态机的第一段采用同步时序描述状态转移
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
curr_st <= S0;
else
curr_st <= next_st;
end
//状态机的第二段采用组合逻辑判断状态转移条件
always @(*) begin
case (curr_st)
S0: next_st = S1;
S1: next_st = S2;
S2: next_st = S3;
S3: next_st = S4;
S4: next_st = S5;
S5: next_st = S6;
S6: next_st = S0;
default: next_st = S0;
endcase
end
//状态机的第三段描述状态输出(这里采用时序电路输出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
clk_divide_7 <= 1'b0;
else if ((curr_st == S0) | (curr_st == S1) | (curr_st == S2) | (curr_st == S3))
clk_divide_7 <= 1'b0;
else if ((curr_st == S4) | (curr_st == S5) | (curr_st == S6))
clk_divide_7 <= 1'b1;
else
;
end
endmodule

4.7 模块化设计

下面以一个实例(静态数码管显示实验)来说明模块和模块之间的例化方法。

计时模块部分代码如下所示:
1 module time_count (2 input clk , // 时钟信号3 input rst_n , // 复位信号45 output reg flag // 一个时钟周期的脉冲信号6 );78 //parameter define9 parameter MAX_NUM = 25000_000 ; // 计数器最大计数值……34 endmodule
1 module seg_led_static (2 input clk , // 时钟信号3 input rst_n , // 复位信号(低有效)45 input add_flag , // 数码管变化的通知信号6 output reg [ 5 : 0 ] sel , // 数码管位选7 output reg [ 7 : 0 ] seg_led // 数码管段选8 );……66 endmodule
1 module seg_led_static_top (2 input sys_clk , // 系统时钟3 input sys_rst_n , // 系统复位信号(低有效)45 output [ 5 : 0 ] sel , // 数码管位选6 output [ 7 : 0 ] seg_led // 数码管段选78 );910 //parameter define11 parameter TIME_SHOW = 25'd25000_000 ; // 数码管变化的时间间隔0.5s1213 //wire define14 wire add_flag ; // 数码管变化的通知信号1516 //*****************************************************17 //** main code18 //*****************************************************1920 //例化计时模块21 time_count #(22 . MAX_NUM ( TIME_SHOW )23 ) u_time_count (24 . clk ( sys_clk ),25 . rst_n ( sys_rst_n ),2627 . flag ( add_flag )28 );2930 //例化数码管静态显示模块31 seg_led_static u_seg_led_static (32 . clk ( sys_clk ),33 . rst_n ( sys_rst_n ),3435 . add_flag ( add_flag ),36 . sel ( sel ),37 . seg_led ( seg_led )38 );3940 endmodule

上图右侧是例化的数码管静态显示模块,子模块名是指被例化模块的模块名,而例化模块名相当于标识,当例化多个相同模块时,可以通过例化名来识别哪一个例化,我们一般命名为“u_”+“子模块名”。信号列表中“.”之后的信号是数码管静态显示模块定义的端口信号,括号内的信号则是顶层模块声明的信号,这样就将顶层模块的信号与子模块的信号一一对应起来,同时需要注意信号的位宽要保持一致。

5 Verilog 编程规范
本节主要给大家介绍下编程规范,良好的编程规范是一个 FPGA 工程师必备的素质。
5.1 编程规范重要性
5.2 工程组织形式
工程的组织形式一般包括如下几个部分,分别是 doc、par、rtl 和 sim 四个部分。
doc:一般存放工程相关的文档,包括该项目用到的 datasheet(数据手册)、设计方案等。不过为了便于大家查看,我们开发板文档是统一汇总存放在资料盘下的;
5.3 文件头声明
//****************************************Copyright (c)***********************************////原子哥在线教学平台:www.yuanzige.com//技术支持:www.openedv.com//淘宝店铺:http://openedv.taobao.com//关注微信公众平台微信号:"正点原子",免费获取 ZYNQ & FPGA & STM32 & LINUX 资料。//版权所有,盗版必究。//Copyright(C) 正点原子 2018-2028//All rights reserved//----------------------------------------------------------------------------------------// File name: led_twinkle// Last modified Date: 2019/4/14 10:55:56// Last Version: V1.0// Descriptions: LED 灯闪烁//----------------------------------------------------------------------------------------// Created by: 正点原子// Created date: 2019/4/14 10:55:56// Version: V1.0// Descriptions: The original version////----------------------------------------------------------------------------------------//****************************************************************************************//
5.4 输入输出定义
1 module led (2 input sys_clk , //系统时钟3 input sys_rst_n , //系统复位,低电平有效4 output reg [ 3 : 0 ] led //4 位 LED 灯5 );
5.5 parameter 定义
7 //parameter define8 parameter WIDTH = 25 ;9 parameter COUNT_MAX = 25_000_000 ; //板载50M时钟=20ns,0.5s/20ns=25000000,需要25bit10 //位宽
5.6 wire/reg 定义
12 //reg define13 reg [ WIDTH - 1 : 0 ] counter ;14 reg [ 1 : 0 ] led_ctrl_cnt ;1516 //wire define17 wire counter_en ;
5.7 信号命名
5.8 always 块描述方式
26 //用于产生0.5秒使能信号的计数器27 always @( posedge sys_clk or negedge sys_rst_n ) begin28 if ( sys_rst_n == 1'b0 )29 counter <= 1'b0 ;30 else if ( counter_en )31 counter <= 1'b0 ;32 else33 counter <= counter + 1'b1 ;34 end
5.9 assign 块描述方式
23 //计数到最大值时产生高电平使能信号24 assign counter_en = ( counter == ( COUNT_MAX - 1'b1 )) ? 1'b1 : 1'b0 ;
5.10 空格和 TAB
5.11 注释
26 //用于产生 0.5 秒使能信号的计数器27 always @( posedge sys_clk or negedge sys_rst_n ) begin28 if ( sys_rst_n == 1'b0 )29 counter <= 1'b0 ;30 else if ( counter_en ) // counter_en 为 1 时,counter 清 031 counter <= 1'b0 ;32 else33 counter <= counter + 1'b1 ;34 end
5.12 模块例化
20 //例化计时模块21 time_count #(22 . MAX_NUM ( TIME_SHOW )23 ) u_time_count (24 . clk ( sys_clk ),25 . rst_n ( sys_rst_n ),2627 . flag ( add_flag )28 );2930 //例化数码管静态显示模块31 seg_led_static u_seg_led_static (32 . clk ( sys_clk ),33 . rst_n ( sys_rst_n ),3435 . add_flag ( add_flag ),36 . sel ( sel ),37 . seg_led ( seg_led )38 );