FPGA之道(34)Verilog初始化与操作符号
前言
Verilog的初始化可以在定义变量时候赋值,也可在initial语句块内赋值,当然也可以在always内的复位时候初始化。
至于Verilog的操作符采用与VHDL操作符并行的方式来描述。
本文摘自《FPGA之道》,一起学习下。
Verilog初始化
初始化主要是针对FPGA内部有记忆的单元,例如寄存器、BLOCK RAM等,而对于无记忆的单元,例如硬件连线,没有必要也无法对它们赋初值。目前来说,并不是所有的FPGA芯片都支持赋初值的,那么对于那些不支持赋初值的FPGA芯片,我们一定要设计好复位电路;而对于支持赋初值的FPGA芯片,我们可以利用这一特性让FPGA在上电后就处于一个可被确定的状态。除此以外,赋初值对于仿真也有类似的影响。
在Verilog语言中,有两种初始化的方法:分布式和集中式,分别介绍如下:
分布式赋初值就是在声明变量的时候顺便赋初值,例如:
reg [7:0] data = 8’h7f;
而集中式赋初值使用了initial语法,这种方法的好处是可以将所有赋初值操作集中在一起,方便日后的维护和修改,因此集中式赋初值的方法也是我们所推荐的赋初值方法。initial语法也是程序块的一种,它和always是程序块“唯二”的两个分支,所不同的是always中的语句是永久的循环执行,而initial中的语句只在程序最开始执行一次。它的语法如下:
initial begin
<statements>;
end
- 1
- 2
- 3
具体使用请参考下例:
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
从上例可以看出,线网类型不支持赋初值。从语法上来说,这是由于程序块中被赋值的只能是寄存器类型的变量;从物理意义上来说,就是只有有记忆的单元才能被赋初值。另外,由于initial中的语句仅执行一次,且不涉及具体功能电路的实现,所以它内部的寄存器类型变量使用阻塞和非阻塞赋值都行,没有什么区别。
Verilog的操作符号
Verilog赋值运算符
连续赋值符号
连续赋值即continues assignments,它常配合assign关键字使用,仅能用于线网类型的变量赋值,语法如下:
assign <variable_name> = ;
例如:
input b;
wire a;
assign a = b;
- 1
- 2
- 3
有时候,为了简便起见,上述我们可以把变量声明和连续赋值合并起来,简写成这样:
wire a = b;
- 1
相比之下,我们可以称带有assign关键字的赋值方式为显式的连续赋值,而简化后的赋值方式为隐式的连续赋值。为什么要叫连续赋值呢?这是因为该赋值语句所描述的硬件功能赋值电路,不需要等待任何事件的触发,也不会受任何事件的影响而中断,会连续不断的执行(当然了,在仿真的时候显然不能这样,否则将进入仿真死循环)。显然,这种模式只能描述组合逻辑,故只能用于线网类型变量的赋值。
阻塞赋值符号
阻塞赋值主要用于组合逻辑,它只能操作寄存器类的变量,赋值符号为“=”。一般来说,当我们期望变量被综合成为FPGA中的硬件连线时,我们常常会对变量用阻塞赋值,而对应的赋值语句一般要写在表示组合逻辑的always程序段中。在always程序段中,语句的执行一般都是顺序的,阻塞赋值符号的作用就是保证上一条语句已执行并且赋值操作完成后才开始下一条语句的执行,也因此得名阻塞赋值。例如:
always@(c)
begin
b = c;
a = b;
end
- 1
- 2
- 3
- 4
- 5
如果当前状态c为逻辑1,b为逻辑1,a也为逻辑1。那么若此时c变为逻辑0,那么则在本次c改变后,b为逻辑0,a也为逻辑0。从硬件实现上来说,上述代码就是三段首尾相连的硬件连线,其实就是一根线,你想,如果连线的输入端值改变了,那么整个连线的值肯定也都跟着变了,所以c处的值传给b,同时经过b也传给了a;从阻塞赋值符号的作用来说,由于在第一条语句的赋值完成后才开始执行第二条语句,所以第二条语句中的b是b的新值。
非阻塞赋值符号
非阻塞赋值主要用于时序逻辑,它也只能操作寄存器类的变量,赋值符号为“<=”。一般来说,当我们期望变量被综合成为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的旧值。
映射赋值符号
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;
- 1
- 2
- 3
- 4
- 5
- 6
&
“&”是逻辑与运算符,它是一个双目运算符,对两个被操纵数做取与逻辑;若被操作数是具有一定位宽的向量,则对两个向量按位取与操作。例如:
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;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
|
“|”是逻辑或运算符,它是一个双目运算符,对两个被操纵数做取或逻辑;若被操作数是具有一定位宽的向量,则对两个向量按位取或操作。例如:
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;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
^
“^”是逻辑异或运算符,它是一个双目运算符,对两个被操纵数做取异或逻辑;若被操作数是具有一定位宽的向量,则对两个向量按位取异或操作。例如:
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;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
~^
“~^”是逻辑异或非运算符(也即同或运算),它是一个双目运算符,对两个被操纵数做取同或逻辑;若被操作数是具有一定位宽的向量,则对两个向量按位取同或操作。例如:
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;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
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;
- 1
- 2
- 3
注意,不要轻易在对应FPGA硬件资源的变量上使用这些运算符,会非常浪费资源。如果一定要使用,可以使用一些现成的IP核,或者自动动手编写相关算法,这样实现起来会更加节省资源、更加可控一些。
**
“”是乘方运算,完成求一个数的n次幂运算,它是双目运算符,且操作数不能任意颠倒,举例如下:
parameter a = 28; // a = 256
注意,不要轻易在能对应FPGA硬件的变量上使用乘方运算符,如果一定要用,可以使用一些现成的IP核,或者自动动手编写相关算法。
Verilog关系运算符
关系运算符主要用来做一些条件判断用的,通用的语法为:
<expression_left> <relation_operator> <expression_right>
Verilog中的关系运算符包括如下几种:
== // 两边表达式相等
=== // 两边表达式全等(包含X与Z状态,仅用于仿真)
!= // 两边表达式不相等
!== // 两边表达式不全等(包含X与Z状态,仅用于仿真)
< // 左边表达式小于右边表达式
<= // 左边表达式小于等于右边表达式
> // 左边表达式大于右边表达式
>= // 左边表达式大于等于右边表达式
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
关系运算符一般不单独使用,都需要配合具体的语句来实现完整的意思。关系表达式会返回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))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
因为判断条件(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;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
如果我们把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;
- 1
- 2
- 3
- 4
- 5
- 6
Verilog迭代连接运算符
Verilog中有一个特殊的运算符号——“{}”,称之为迭代连接运算符,顾名思义,它兼具迭代和连接的双重功效,介绍如下:
连接功能
该运算符号的第一个基本功能是连接功能,能够将若干个寄存器或者线网类型的变量首尾连接起来组成一个位宽更大的变量。例如:
wire a = 1'b1;
wire [2:0] b = 3'b001;
wire [3:0] c = {a, b}; //c = 4'b1001
- 1
- 2
- 3
迭代功能
迭代功能是该运算符号的第二个基本功能,它能够把一个变量复制多次,然后首尾连接起来组成一个位宽更大的变量。例如:
wire [1:0] a = 2'b10;
wire [7:0] b = {4{a}}; //c = 8'b10101010
- 1
- 2
注意,最外层的大括号是不能省略的!里、外两层大括号是语法的必须要求!因为上述赋值其实相当于:
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
当然了,还可以有更复杂的组合,这里就不再赘述了。
迭代连接运算符号除了可以操作变量外,也可以操作常量,例如,如果我们要给一个64bits的线网向量赋一个64bits的常量如下:
wire [63:0] a = 64'hF9F9F9A8A8A8A8A8;
可以利用迭代写成
wire [63:0] a = {{3{8'hF9}}, {5{8'hA8}}};
- 1
- 2
- 3
Verilog移位运算符
Verilog中共有两类移位运算符号,即有符号移位和无符号移位。每一类中又有左移和右移之分,列举如下:
<< // (无符号)左移
<<< // 带符号左移
>> // (无符号)右移
>>> // 带符号右移
- 1
- 2
- 3
- 4
无符号移位的意思即是在移位的时候空出来的位用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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
可以发现,对于上例来说,有符号和无符号移位的结果相同,这是由于端口默认的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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
顺便说一下,移位操作符所能完成的功能用迭代连接运算符都能完美的完成,例如:
wire [3:0] a,b;
assign a = b << 2;
可以写成
assign a = {b[1:0], 2’b00};
- 1
- 2
- 3
- 4
而且通过迭代连接运算符,我们还能定义更多样化的移位,例如用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);
文章来源: reborn.blog.csdn.net,作者:李锐博恩,版权归原作者所有,如需转载,请联系作者。
原文链接:reborn.blog.csdn.net/article/details/104303981
- 点赞
- 收藏
- 关注作者
评论(0)