Verilog语法概述(一)

(一)这是我的第一篇blog,有一点激动捏!

  第一次写这类文章,如有不当之处,欢迎大家批评指正!感谢wwd同学的指正!

关于开发工具

  推荐使用Vivado + ModelSim 或 Quartus Ⅱ + ModelSim,这基本上就可以完成大部分工程。

关于练习

  推荐一个Verilog练习网站:HDLBits

(二)为什么选择Verilog?

  目前主流的HDL(Hardware Description Language)有VHDL和Verilog。VHDL语法比较严谨;而Verilog类似C语言,风格相对自由。本质上,语言之间其实并不存在优劣之分,只有适合与否。如果你有一定的C语言基础,不妨先学习Verilog,毕竟谁不想轻松的度过语法适应期呢?不过呢,当你掌握一门语言之后,最好还是了解一下另一门语言,毕竟搬砖也是一项技术活!

(三)HDL语言不是软件语言!

  相信大家或多或少有利用C语言或其它软件语言进行编程,程序是顺序执行的。但是HDL语言描述的是硬件电路,而硬件电路是并行处理的,这一转变或许会成为许多初学者的绊脚石。因此我们在学习语法的同时要做到心中有电路,而不仅仅是一串代码。那么,怎么判断代码描述的电路是否正确呢?写完一段代码后,多花一些精力对比实际逻辑电路(切忌偷懒哦),必要时可以做一些仿真,条件允许的话,可以在实验板上做一些测试。假以时日,代码与电路定会成为相伴而行的“老夫妻”。

(四)可综合语法与行为级语法

  可实现为硬件电路的语法称为可综合语法;不能实现到硬件电路中,却常常可作为仿真验证的高层次语法称为行为级语法。这一区分,使得入门的难度降低了许多,因为可综合语法只是一个很小的子集,或许当你完成一个实际系统设计时,你会惊奇的发现也就十几条语法。
  行为级语法很无奈的说:“要不你试试用我做仿真吧。”
在这里插入图片描述
  值得注意的是,本篇将只会对基本语法进行简单的介绍。详细的讲解带来的多半是困倦,实战中踩下的每一个坑都将促使你成长。那么,让我们开始一段新的学习之旅吧!

可综合的语法子集

  下面是一些常用的RTL级的Verilog语法及其简单的用法描述。我们可以将Verilog与C语言类比记忆,但是,一定要牢记Verilog描述的是并行处理的硬件电路。请注意,代码示例中的<>符号是为了标识中文,并无其它特殊含义。

(1)模块声明类语法:module…endmodule

  该语法在每一个Verilog文件中都会出现,是一个相对固定的用法,格式如下:

module module_name(<端口信号列表>...);
	<逻辑代码>...
endmodule

  值得注意的是,module_name是模块的名称,可以是数字、下划线和字母的组合。为了后续维护方便,尽量利用模块英文名称的缩写命名或者根据用途差分模块命名。端口信号列表中应当罗列出该模块所有的输入/输出端口信号名。

(2)端口声明:input, output, inout(inout用法比较特殊,将在后续实例中详细介绍)

  我么可以将硬件电路分为许多的部分,每个部分完成指定的功能,也就是我们前面提到的module。很显然,不同的module之间需要通过信号线连接。对于某一个module来说,这些信号便可以分为输入(input)信号、输出(output)信号和双向(inout)信号。
  你肯定还没有忘的是:你可以在module语法的端口信号列表中声明该模块用于与外部接口的信号。你将会面临两个选择:

  1. 在端口信号列表中直接声明输入输出类型。
  2. 在逻辑代码部分声明端口的输入输出类型。

  具体如下:

// 1. 在端口信号列表中直接声明输入输出类型。
module module_name(input clk, input rst_n, output [7:0] data_out);
	<逻辑代码>...
endmodule

// 2. 在逻辑代码部分声明端口的输入输出类型。
 module module_name(clk, rst_n, data_out);
	input clk;
	input rst_n;
	output [7:0] data_out;
	...
endmodule

  第一个和第二个声明均表示1bit的输入信号端口,而第三个表示8bits的名称为data_out的输出信号。另外,对于我来说,我更加习惯于第一种选择,即在端口信号列表中直接声明输入输出类型,因此之后的例子都将以第一种方式呈现。

(3)参数定义:parameter

  parameter主要用来声明一些常量(虽说是常量,但可以利用defparam修改,我们可以先不考虑),使得模块移植和升级变得更加方便,同时,可读性大大提高。反正我才不想因为一个参数的修改去翻看我那又臭又长的代码!
  仍然以前面的代码为例:

// 2. 在逻辑代码部分声明端口的输入输出类型。
 module module_name(clk, rst_n, data_out);
	input clk;
	input rst_n;
	output [7:0] data_out;

	parameter PERIOD = 1000000;
endmodule

  可以看到,此处我采用大写字母进行参数定义,这是为了与普通内部信号进行区分,另外,参数命名时仍然建议体现参数的含义。

(4)信号类型:wire, reg 等

  这是一个比较重要的概念。累了的话,大可以休息一下,再接着战斗。其实是我累了,哈哈哈哈哈。
  如下图RTL电路所示,在时钟上升沿到来时,reg将会锁存最新的输入数据,而wire就是这两个reg之间的直接连线。
reg与wire示意图
  值得注意的是:作为 input 或 inout 的信号端口只能是wire类型,而output则可以是wire也可以是reg。
  一些常见用法如下:

// 定义一个wire信号
wire <变量名>;

// 给一个定义的wire信号直接连接赋值
wire <变量名> = <常量或变量赋值>;
// 这实际上等价于如下形式:
wire <变量名>;
assign <变量名> =  <常量或变量赋值>;

// 定义一个多bit的wire信号
wire [<最高位>,<最低位>] <变量名>;

// 定义一个reg信号
reg <变量名>

// 对reg信号赋初值
// 这与前面所说的wire信号赋值是不同的
// wire的赋值将在右边操作数发生变化时便重新执行
reg <变量名> = <初始值>

// 定义一个多bit的reg信号
reg [<最高位>,<最低位>] <变量名>;

// 定义一个赋初值的多bit的reg信号
reg [<最高位>,<最低位>] <变量名> = <初始值>;

// 定义一个二维的多bit的reg信号,可以想象成一个矩阵
reg [<最高位>,<最低位>] <变量名> [<最高位>,<最低位>];
(5)多语句定义:begin…end

  这其实很好理解,就类似与C语言中的“{ }”。主要有两种形式:

// 含有命名的begin语句
begin : <块名>
	// 逻辑
end

// 基本的begin语句
begin : 
	// 逻辑
end
(6)比较判断:if…else, case…default…endcase

  这与C语言也是非常类似的,但是我们仍然需要特别注意硬件电路是并行执行的。那么,该语法应该对应到什么样的电路呢?直接举个例子吧。

// 以下代码实现了在clk上升沿到来时,输出两个输入中的较小值
module test(
    input din1,
    input din2,
    input clk,
    output reg dout
    );
    always@(posedge clk)
    begin
        if(din1 < din2)
            dout <= din1;
        else if(din1 > din2)
            dout <= din2;
        else
            dout <= dout;
    end
endmodule

  相对应的RTL级电路如图所示,我们可以发现if…else语句可通过多路复用器实现,并实现并行计算。同时,我们还可以发现,两个复用器的控制信号均为RTL_LT的输出,这是由于上述代码中的小于判断具备更高的优先级。大家可以自行与C语言中的if…else语句作一个比较,相信大家会对并行执行与顺序执行有更加深入的理解。

在这里插入图片描述
  
  
  case语句与if…else的硬件实现并没有什么本质不同。与C语言中类似的是:若分支较多则可以考虑采用case语法,原因何在?将上述代码做一些修改,并查看其RTL级电路:

module test(
    input din1,
    input din2,
    input clk,
    output reg dout
    );
    wire temp;
    assign temp = (din1<din2);
    always@(posedge clk)
    begin
        case(temp)
            1'b1:dout <= din1;
            1'b0:dout <= din2;
            default:dout <= dout;
        endcase
    end
endmodule

在这里插入图片描述
  我们发现使用case语句占用的资源变少了,这是因为case语句中的各分支不具有优先级。
  因此,如果你的设计中对各分支条件并没有优先级的要求,完全可以考虑采用case语句;反之,if…else语句将会成为较优选择。

(7)循环语句:for

  for循环用的比较少,且结构与C语言类似,其示例如下:

for{<变量名> = <初始值>; <判断表达式>; <变量名> = <新值>}
begin
	<具体逻辑>
end

  读者可以尝试编写一段简单的for循环代码,并查看其RTL级电路。

(8)任务定义:task…endtask

  为了便于理解,我们可以将task与C语言中的子函数类比。task中也可以有input、output 和 inout端口作为出入口参数,并可以实现时序控制。值得注意的是:C语言子函数可以有返回值,而task是没有返回值的。但其出口参数可以实现参数返回,类似于C语言中的指针。基本用法如下:

task <task_name>
	// 可选声明部分,例如localparam
	begin
	// 具体逻辑
	end
endtask

  在这里依然是简要介绍,将在具体实例中展示其具体用法。

(9)连续赋值:assign 和 A?B:C

  assign,按照我浅薄的知识理解,我们可以把等号两边的信号用导线相连接,这与你在现实中拿导线把两个端口连接起来没有区别,这便是赋值的概念。也因此,这样的连接跟时序无关,只要电压的值改变了另一端的值也会同时改变,这便是连续的概念。所以只能利用assign对wire类型变量进行赋值,而不能对reg变量赋值。用法如下:

assign <wire变量名> = <变量或常量>;

  对于“?:”,这本质上就是一个简单的if…else语句。区别在于:“?:”主要用在组合逻辑中(可搭配assign使用),而if…else时常用在时序逻辑中。例如:

assign dout = (din1>din2)?din1:din2;

我们可以看看其RTL级电路与(6)有什么区别?
?:电路图
  可以发现该电路与(6)中case语句对应的电路仅相差了一个触发器,这说明“?:”语句也不具有优先级。另外,虽然只相差一个触发器,但实现的功能却相差甚远,读者可自行思考(可考虑赋值的时刻)。

(10)always模块:

  用法如下:

always@(<敏感表>)
begin
	<具体逻辑>
end

  敏感表可以为电平、边沿(上升沿posedge/下降沿negedge),也可以是“ * ”,这代表所有输入信号。
  一般来说,如果敏感表为电平,则表示电平发生变化时,将执行此块逻辑;如果敏感表中有边沿信号,将在到达指定边沿时执行逻辑,因此多为时序逻辑。

(11)运算操作符:

  各种逻辑操作符、移位操作符、算术操作符大多是可综合的。如下所示:

+				//加
-				//减
!				//逻辑非
~				//取反,注意与!区别
&				//位与
~&				//与非
|				//位或
~|				//或非
^				//异或
^~				//同或
~^				//同或
*				//乘,是否可综合需要看综合工具
/				//除,是否可综合需要看综合工具,一般要求除数为整数,且为2的指数
%				//取模
<<				//逻辑左移
>>				//逻辑右移,高位补零
<				//小于
<=				//小于等于
>				//大于
>=				//大于等于
==				//逻辑相等,仅能判断0与1是否相等,若存在高阻态或不定态,则结果为不定态。若存在不定态或高阻态,在仿真中可以考虑使用全等符号===,但其不可综合。
!=				//逻辑不等,可与==类比
&&				//逻辑与
||				//逻辑或
{}              //位拼接符,可将两个或多个信号的某些位拼接起来进行运算。
(12)赋值符号:= 和 <=

  “=”和“<=”分别表示阻塞和非阻塞赋值,将在后续实例中进一步介绍。

(五)结束语

  可综合语法只是Verilog中很小的一个子集,硬件设计的精髓是用最简单的语句描述最复杂的硬件。因此,对于做RTL级设计来说,掌握好上面这些基本语法是非常非常重要的!
  下一篇将谈谈代码风格与书写规范,这一问题并没有标准答案,但好的代码风格和书写规范可以有效的提高FPGA设计的效率。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值