FPGA之道(34)Verilog初始化与操做符号

2021年11月25日 阅读数:4
这篇文章主要向大家介绍FPGA之道(34)Verilog初始化与操做符号,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

前言

Verilog的初始化能够在定义变量时候赋值,也可在initial语句块内赋值,固然也能够在always内的复位时候初始化。
至于Verilog的操做符采用与VHDL操做符并行的方式来描述。
本文摘自《FPGA之道》,一块儿学习下。python

Verilog初始化

初始化主要是针对FPGA内部有记忆的单元,例如寄存器、BLOCK RAM等,而对于无记忆的单元,例如硬件连线,没有必要也没法对它们赋初值。目前来讲,并非全部的FPGA芯片都支持赋初值的,那么对于那些不支持赋初值的FPGA芯片,咱们必定要设计好复位电路;而对于支持赋初值的FPGA芯片,咱们能够利用这一特性让FPGA在上电后就处于一个可被肯定的状态。除此之外,赋初值对于仿真也有相似的影响。
在Verilog语言中,有两种初始化的方法:分布式和集中式,分别介绍以下:
分布式赋初值就是在声明变量的时候顺便赋初值,例如:
reg [7:0] data = 8’h7f;
而集中式赋初值使用了initial语法,这种方法的好处是能够将全部赋初值操做集中在一块儿,方便往后的维护和修改,所以集中式赋初值的方法也是咱们所推荐的赋初值方法。initial语法也是程序块的一种,它和always是程序块“惟二”的两个分支,所不一样的是always中的语句是永久的循环执行,而initial中的语句只在程序最开始执行一次。它的语法以下:web

initial begin
	<statements>;
end

具体使用请参考下例:算法

	wire a;
reg [7:0] b;
reg [7:0] c[99:0];
integer i;
initial begin
		a = 1'b1;// wrong
		b <= 8'h7E;
		for(i=0; i <100; i = i+1) begin
			c[i] = i;
		end
end

从上例能够看出,线网类型不支持赋初值。从语法上来讲,这是因为程序块中被赋值的只能是寄存器类型的变量;从物理意义上来讲,就是只有有记忆的单元才能被赋初值。另外,因为initial中的语句仅执行一次,且不涉及具体功能电路的实现,因此它内部的寄存器类型变量使用阻塞和非阻塞赋值都行,没有什么区别。express

Verilog的操做符号

Verilog赋值运算符

连续赋值符号

连续赋值即continues assignments,它常配合assign关键字使用,仅能用于线网类型的变量赋值,语法以下:
assign <variable_name> = ;
例如:编程

input b;
wire a;
assign a = b;

有时候,为了简便起见,上述咱们能够把变量声明和连续赋值合并起来,简写成这样:分布式

wire a = b;

相比之下,咱们能够称带有assign关键字的赋值方式为显式的连续赋值,而简化后的赋值方式为隐式的连续赋值。为何要叫连续赋值呢?这是由于该赋值语句所描述的硬件功能赋值电路,不须要等待任何事件的触发,也不会受任何事件的影响而中断,会接二连三的执行(固然了,在仿真的时候显然不能这样,不然将进入仿真死循环)。显然,这种模式只能描述组合逻辑,故只能用于线网类型变量的赋值。svg

阻塞赋值符号

阻塞赋值主要用于组合逻辑,它只能操做寄存器类的变量,赋值符号为“=”。通常来讲,当咱们指望变量被综合成为FPGA中的硬件连线时,咱们经常会对变量用阻塞赋值,而对应的赋值语句通常要写在表示组合逻辑的always程序段中。在always程序段中,语句的执行通常都是顺序的,阻塞赋值符号的做用就是保证上一条语句已执行而且赋值操做完成后才开始下一条语句的执行,也所以得名阻塞赋值。例如:学习

always@(c)
begin
		b = c;
		a = b;
end

若是当前状态c为逻辑1,b为逻辑1,a也为逻辑1。那么若此时c变为逻辑0,那么则在本次c改变后,b为逻辑0,a也为逻辑0。从硬件实现上来讲,上述代码就是三段首尾相连的硬件连线,其实就是一根线,你想,若是连线的输入端值改变了,那么整个连线的值确定也都跟着变了,因此c处的值传给b,同时通过b也传给了a;从阻塞赋值符号的做用来讲,因为在第一条语句的赋值完成后才开始执行第二条语句,因此第二条语句中的b是b的新值。ui

非阻塞赋值符号

非阻塞赋值主要用于时序逻辑,它也只能操做寄存器类的变量,赋值符号为“<=”。通常来讲,当咱们指望变量被综合成为FPGA中的寄存器时,咱们经常会对变量用非阻塞赋值符号,固然了,对应的赋值语句也应该写在表示时序逻辑的always程序段中。在always程序段中,语句的执行通常都是顺序的,非阻塞赋值符号的做用就是能够在上一条语句已执行可是赋值操做还没完成前就开始下一条语句的执行。例如:
always@(posedge clk)
begin
b <= c;
a <= b;
end
若是在上一次时钟上升沿触发后,b为逻辑1,a也为逻辑1。那么若此时c保持为逻辑0,那么在本次时钟上升沿触发后,b为逻辑0,a为上一次b的值,即逻辑1。从硬件实现上来讲,上述代码就是一个移位寄存器,每一次时钟触发后,c处的值传给b,b处的值传给a,值的传递只能跨越1个寄存器;从非阻塞赋值符号的做用来讲,因为在第一条语句的赋值尚未完成时,就开始执行第二条语句,因此第二条语句中的b仍然使用的是b的旧值。url

映射赋值符号

Veriog中的映射赋值符号为“.”,它不能算是一种纯粹的赋值运算符,由于它只是在两个变量之间创建一种链接的关系,而自己并不明确代表赋值的方向关系,即究竟是把左边的数据写到右边去仍是把右边的数据写到左边去。最多见的例子就是模块实例化语句,不管端口方向是input仍是output,都一概使用“.”来映射赋值,至于赋值的方向,编译器会根据上下文本身去推断。例如:
wire line0, line1, result;
cellAnd m0(.a (line0),
.b (line1),
.c (result));

位置赋值

位置赋值没有任何显式的操做符号,它仅仅是根据变量书写的位置来肯定赋值的对应关系,所以,位置赋值实际上是映射赋值的一个简化版本。例如,映射赋值小节里面的例子也能够写成这样:
cellAnd m0(line0,line1, result);
不过对于凡是支持“.”符号来进行映射赋值地方,建议你们仍是不要使用位置赋值,由于映射赋值是位置无关的,这样会在很大程度上减小程序出错的可能性,而且方便往后的修改与维护。
最后注意,位置赋值和映射赋值不能混合使用!

Verilog按位运算符

按位运算符是一类最基本的运算符,能够认为它们直接对应数字逻辑中的与、或、非门等逻辑门,在Verilog中它们的描述以下:

~

“~”是逻辑非运算符,它是一个单目运算符,对被操纵数作取反逻辑;若被操做数是具备必定位宽的向量,则对该向量按位取反。例如:

wire a, b;
assign b = 1'b0;
assign a = ~ b; // a = 1'b1
wire [7:0] busA, busB;
assign busB = 8'h00;
assign busA = ~ busB; // busA = 8'hFF;

&

“&”是逻辑与运算符,它是一个双目运算符,对两个被操纵数作取与逻辑;若被操做数是具备必定位宽的向量,则对两个向量按位取与操做。例如:

wire a, b, c;
assign a = 1'b1;
assign b = 1'b1;
assign c = a & b; // c = 1'b1
wire [7:0] busA, busB, busC;
assign busA = 8'hF1;
assign busB = 8'h1F;
assign busC = busA & busB; // busC = 8'h11;

|

“|”是逻辑或运算符,它是一个双目运算符,对两个被操纵数作取或逻辑;若被操做数是具备必定位宽的向量,则对两个向量按位取或操做。例如:

wire a, b, c;
assign a = 1'b0;
assign b = 1'b1;
assign c = a | b; // c = 1'b1
wire [7:0] busA, busB, busC;
assign busA = 8'hF1;
assign busB = 8'h1F;
assign busC = busA | busB; // busC = 8'hFF;

^

“^”是逻辑异或运算符,它是一个双目运算符,对两个被操纵数作取异或逻辑;若被操做数是具备必定位宽的向量,则对两个向量按位取异或操做。例如:

wire a, b, c;
assign a = 1'b1;
assign b = 1'b1;
assign c = a ^ b; // c = 1'b0
wire [7:0] busA, busB, busC;
assign busA = 8'hF1;
assign busB = 8'h1F;
assign busC = busA ^ busB; // busC = 8'hEE;

~^

“~^”是逻辑异或非运算符(也即同或运算),它是一个双目运算符,对两个被操纵数作取同或逻辑;若被操做数是具备必定位宽的向量,则对两个向量按位取同或操做。例如:

wire a, b, c;
assign a = 1'b0;
assign b = 1'b0;
assign c = a ~^ b; // c = 1'b1
wire [7:0] busA, busB, busC;
assign busA = 8'hF1;
assign busB = 8'h1F;
assign busC = busA ~^ busB; // busC = 8'h11;

Verilog归约运算符

归约运算符,都是单目运算符号,它们的被操做数都是有必定位宽的向量,而操做的结果都是1bit的。因为它们和按位运算符很是的类似,因此使用的时候必定要注意区分。分别介绍入下:

&

在这里,“&”是归约与运算符,它对一个被操纵数的全部位作逻辑与操做。例如:
wire [3:0] bus = 4’hF;
wire result = & bus; // result = 1’b1;
若是用按位运算符来表示,上述赋值至关于:
assign result = bus[0] & bus[1] & bus[2] & bus[3];

~&

在这里,“~&”是归约与非运算符,它对一个被操纵数的全部位先作归约与操做,而后对结果取逻辑反操做。例如:

wire [3:0] bus = 4’hF;
wire result = ~& bus; // result = 1’b0;
若是用按位运算符来表示,上述赋值至关于:
assign result = ~ (bus[0] & bus[1] & bus[2] & bus[3]);

|

在这里,“|”是归约或运算符,它对一个被操纵数的全部位作逻辑或操做。例如:

wire [3:0] bus = 4’h7;
wire result = | bus; // result = 1’b1;
若是用按位运算符来表示,上述赋值至关于:
assign result = bus[0] | bus[1] | bus[2] | bus[3];

~|

在这里,“~|”是归约或非运算符,它对一个被操纵数的全部位先作归约或运算,而后对结果取逻辑反操做。例如:

wire [3:0] bus = 4’h7;
wire result = ~| bus; // result = 1’b0;
若是用按位运算符来表示,上述赋值至关于:
assign result = ~ (bus[0] | bus[1] | bus[2] | bus[3]);

^

在这里,“^”是归约异或运算符,它对一个被操纵数的全部位作逻辑异或操做。例如:

wire [3:0] bus = 4’h7;
wire result = ^ bus; // result = 1’b1;
若是用按位运算符来表示,上述赋值至关于:
assign result = bus[0] ^ bus[1] ^ bus[2] ^ bus[3];

~^

在这里,“~^”是归约异或非运算符(注意,只有当操做数的位宽等于2时,异或非才等同于同或),它对一个被操纵数的全部位先作归约异或运算,而后对结果取逻辑反操做。例如:

wire [3:0] bus = 4’h7;
wire result = ~^ bus; // result = 1’b0;
若是用按位运算符来表示,上述赋值至关于:
assign result = ~ (bus[0] ^ bus[1] ^ bus[2] ^ bus[3]);

Verilog算数运算符

算数运算符,即咱们常说的加、减、乘、除等,这类运算符的抽象层级较高,从数字逻辑电路实现上来看,它们都是基于与、或、非等基础门逻辑组合实现的。FPGA中的算数运算符主要有两种用途:一种是做用于具体的硬件的信号上,完成相关的运算功能;另外一种是做用于Verilog程序的某些参数或者语句结构上,仅仅起到帮助用户更方便的编写代码和帮助编译器更好的理解代码的做用。
它们在Verilog中的描述以下:

+

“+”是加法运算符,它是一个双目运算符,即必须有两个操做数,对它们进行加法运算。例如:

wire [3:0] a = 4’d3;
wire [3:0] b = 4’d2;
wire [3:0] c = a + b; // c = 4’d5
wire [3:0] d = 4’d15;
wire [3:0] e = b + d; // e = 4’d1, 溢出的高位被丢弃

-

“-”是减法运算符,它是一个双目运算符,即必须有两个操做数(两个操做数的地位不同,一个是被减数,一个是减数,不能颠倒),对它们进行减法运算。例如:

wire [3:0] a = 4’d3;
wire [3:0] b = 4’d2;
wire [3:0] c = a - b; // c = 4’d1
wire [3:0] d = b - a; // c = 4’d15,若是以有符号2进制来理解,4’d15等价于-1

*

“*”是乘法运算符,它是一个双目运算符,即必须有两个操做数,对它们进行乘法运算。例如:

wire [7:0] a = 8’d10;
wire [7:0] b = 8’d12;
wire [7:0] c = a * b; // c = 8’d120

/

“/”是除法运算符,它是一个双目运算符,即必须有两个操做数(两个操做数的地位不同,一个是被除数,一个是除数,不能颠倒),对它们进行除法运算。例如:

wire [7:0] a = 8’d140;
wire [7:0] b = 8’d20;
wire [7:0] c = a / b; // c = 8’d7

其实FPGA中的乘、除法都是按照二进制数来进行的,关于二进制数的乘、除法的详细阐述。

%

“%”是求余数运算符,它是双目运算符,且操做数不能任意颠倒,例如:

wire [7:0] a = 8'd55;
wire [7:0] b = 8'd3;
wire [7:0] c = a % b; // c = 8'd1;

注意,不要轻易在对应FPGA硬件资源的变量上使用这些运算符,会很是浪费资源。若是必定要使用,可使用一些现成的IP核,或者自动动手编写相关算法,这样实现起来会更加节省资源、更加可控一些。

**

”是乘方运算,完成求一个数的n次幂运算,它是双目运算符,且操做数不能任意颠倒,举例以下:
parameter a = 2
8; // a = 256
注意,不要轻易在能对应FPGA硬件的变量上使用乘方运算符,若是必定要用,可使用一些现成的IP核,或者自动动手编写相关算法。

Verilog关系运算符

关系运算符主要用来作一些条件判断用的,通用的语法为:
<expression_left> <relation_operator> <expression_right>
Verilog中的关系运算符包括以下几种:

==		// 两边表达式相等
===		// 两边表达式全等(包含X与Z状态,仅用于仿真)
!=		// 两边表达式不相等
!==		// 两边表达式不全等(包含X与Z状态,仅用于仿真)
<		// 左边表达式小于右边表达式
<=		// 左边表达式小于等于右边表达式
>		// 左边表达式大于右边表达式
>=		// 左边表达式大于等于右边表达式

关系运算符通常不单独使用,都须要配合具体的语句来实现完整的意思。关系表达式会返回true或者false的结果。即,若是左右表达式知足相应关系运算符表示的关系时,返回true,不然返回false。
因为Verilog借鉴了不少C语言的元素,因此在条件判断的时候支持简写,例如:

wire a;
if (a != 1'b0)
能够简写成
if (a)if (a == 1'b0)
能够利用按位取反操做符写成
if (~a) // 至关于 if ((~a) != 1'b0)
或者利用逻辑非操做符(下一小节介绍)写成
if (!a) // 至关于 if (!(a != 1'b0))
注意,若操做数为具备必定位宽的向量,则不能利用~符号来完成判断,例如:
wire [3:0] b;
if (b == 4'b0)
不能写成
if (~b) // 至关于 if ((~b) != 4'b0)
只能写成
if (!b) // 至关于 if (!(b != 4'b0))

由于判断条件(b == 4’b0)与(~b) != 4’b0并不等价,例如对于b = 1100来讲,前一个为假后一个为真。

Verilog逻辑运算符

逻辑运算符是链接多个关系表达式用的,从而实现更加复杂的判断,通常也不单独使用,都须要配合具体语句来实现完整的意思。它的语法以下:
<logical_operator> < relation_expression_right>
或者
<relation_expression_left> <logical_operator> < relation_expression_right>
Verilog中的逻辑运算符包括以下几种:
! // 右边表达式的逻辑结果取逻辑反,这是一个单目的操做符
&& // 左右两边表达式的逻辑结果取逻辑与,即同为true才返回true,不然返回false
|| // 左右两边表达式的逻辑结果取逻辑或,即同为false才返回false,不然返回true
结合关系运算符和逻辑运算符,给出如下几个例子:

if (a > b) //for combinational logic 
	max = a; 
else 
	max = b;

if(a > 90 && a < 110) //for sequential logic
	stable <= 1'b1;
else
	stable <= 1'b0;

若是咱们把true当作1’b1,把false当作1’b0,那么Verilog逻辑运算符与按位运算符的前三个的功能是同样的,虽然它们处理的数据类型不同,可是若是从硬件实现角度上来讲,它们是没有区别的,例如:

if (a = 1'b1 && b = 1'b1) //for combinational logic 
	c = 1'b1;
else
	c = 1'b0;
等价于
assign c = a & b;

Verilog迭代链接运算符

Verilog中有一个特殊的运算符号——“{}”,称之为迭代链接运算符,顾名思义,它兼具迭代和链接的双重功效,介绍以下:

链接功能

该运算符号的第一个基本功能是链接功能,可以将若干个寄存器或者线网类型的变量首尾链接起来组成一个位宽更大的变量。例如:

wire a = 1'b1;
wire [2:0] b = 3'b001;
wire [3:0] c = {
   
   
   
    
    a, b}; //c = 4'b1001

迭代功能

迭代功能是该运算符号的第二个基本功能,它可以把一个变量复制屡次,而后首尾链接起来组成一个位宽更大的变量。例如:

wire [1:0] a = 2'b10;
wire [7:0] b = {
   
   
   
    
    4{
   
   
   
    
    a}}; //c = 8'b10101010

注意,最外层的大括号是不能省略的!里、外两层大括号是语法的必需要求!由于上述赋值其实至关于:
wire [7:0] b = {a, a, a, a}; //c = 8’b10101010
其中4{a} 至关于a, a, a, a
因此里层的大括号完成的是屡次复制,而外层的大括号完成的是链接功能,两层大括号配合起来才完成了迭代功能。

混合功能

有时候,咱们可能会碰到一些比较复杂的状况,这时候单独的链接或者迭代功能是不能知足要求的,这时候,就须要咱们来同时使用该符号的链接和迭代功能。例如:

wire a = 1'b1;
wire [2:0] b = 3'b011;
wire [7:0] c = {
   
   
   
    
    {
   
   
   
    
    5{
   
   
   
    
    a}}, b}; //c = 8'b11111011
注意,要保证迭代运算符的完整性,不能写成
c = {
   
   
   
    
    5{
   
   
   
    
    a}, b}; //will cause compile error!
上述例子是先迭代再链接,也能够先链接再迭代,例如
wire [7:0] d = {
   
   
   
    
    2{
   
   
   
    
    a, b}}; //c = 8'b10111011

固然了,还能够有更复杂的组合,这里就再也不赘述了。

迭代链接运算符号除了能够操做变量外,也能够操做常量,例如,若是咱们要给一个64bits的线网向量赋一个64bits的常量以下:

wire [63:0] a = 64'hF9F9F9A8A8A8A8A8;
能够利用迭代写成
wire [63:0] a = {
   
   
   
    
    {
   
   
   
    
    3{
   
   
   
    
    8'hF9}}, {5{8'hA8}}};

Verilog移位运算符

Verilog中共有两类移位运算符号,即有符号移位和无符号移位。每一类中又有左移和右移之分,列举以下:

<<		// (无符号)左移
<<<		// 带符号左移
>>		// (无符号)右移
>>>		// 带符号右移

无符号移位的意思便是在移位的时候空出来的位用1’b0填充;
有符号移位的意思比较晦涩。首先,有符号左移与无符号左移没什么区别,由于,符号位被移出了,低位仍是填充1’b0;而有符号右移时,符号位会向右扩展进行填充。
移位运算符的语法以下:
<shift_operator> <shift_number>
针对上述四种移位操做符举例以下:

input [3:0] m;
output [3:0] n0,n1,n2,n3;
assign m = 4'b1011; 
assign n0 = m << 2; // n0 = 4'b1100
assign n1 = m >> 2; // n1 = 4'b0010
assign n2 = m <<< 2;// n2 = 4'b1100
assign n3 = m >>> 2;// n3 = 4'b0010

能够发现,对于上例来讲,有符号和无符号移位的结果相同,这是因为端口默认的wire类型以及reg类型都被理解为无符号数。若是换成有符号的integer类型,能够看出这两类移位操做符号结果的不一样。例如:

output integer d0,d1,d2,d3;
integer c;
initial begin
	c = 32'h80000001;    	// c = 32'b10000000_00000000_00000000_00000001
end						// 最高位为1,也表示这是一个负数
always@(*)begin
	d0 = c << 1;         	// d0 = 32'b00000000_00000000_00000000_00000010
	d1 = c >> 1;         	// d1 = 32'b01000000_00000000_00000000_00000000
	d2 = c <<< 1;        	// d2 = 32'b00000000_00000000_00000000_00000010
	d3 = c >>> 1;        	// d3 = 32'b11000000_00000000_00000000_00000000
end 

顺便说一下,移位操做符所能完成的功能用迭代链接运算符都能完美的完成,例如:

wire [3:0] a,b; 
assign a = b << 2;
能够写成
assign a = {
   
   
   
    
    b[1:0], 2’b00};

并且经过迭代链接运算符,咱们还能定义更多样化的移位,例如用1’b1填充空位。因此在平常的编程中,咱们彻底能够不去使用这四个移位操做符。

Verilog条件运算符

Verilog语言为了让连续赋值的功能更加完善,因而又从C语言中引入了条件操做符——“? :”,来构建从两个输入中选择一个做为输出的条件选择结构,功能等同于always中的if-else语句。它的基本语法以下:
assign = <1-bit_select> ? <input_for_1> : <input_for_0>;
例如:
wire a, b, sel, c;
assign c = sel ? a : b;
也能够写成
assign c = (sel == 1’b1) ? a : b;
除此之外,条件运算符还支持嵌套使用,从而实现从多个输入中选择一个做为输出,例如:
wire a, b, c, sel0, sel1, c;
assign c = sel0 ? a : (sel1 ? b : c);

本文同步分享在 博客“李锐博恩”(CSDN)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。