目录
1.非阻塞(Non-Blocking)赋值方式(如b <= a)
1. 模块的结构
Verilog的基本设计单元是“模块(block)”。一个模块是由两部分组成的,一部分描述接口,另一部分描述逻辑功能,即定义输入是如何影响输出的。
示例如下:
module block(a,b,c,d);
input a,b;
output c,d;
assign c = a|b;
assign d = a&b;
endmodule
1.1 模块的端口定义
模块的端口声明了模块的输入输出口,其格式如下:
module 模块名(口1,口2,口3,口4,......);
1.2 模块内容
模块的内容包括I/O说明,内部信号声明、功能定义。
- I/O说明的格式如下:
module module_name(input port1,input port2,...output port1,output port2...)
- 内部信号说明
reg [width-1:0] R变量1,R变量2,....
wire [width-1:0]W变量1,W变量2....
- 功能定义
模块中最重要的部分是逻辑功能定义部分。有三种方法可在模块中产生逻辑。
- 用”assign“声明语句,如
assign a = b&c;
- 用实例元件,如
and and_inst(q,a,b);
- 用“always”块,如
always @(posedge clk or posedge clr)
begin
if(clr)
q <= 0;
else
q<= d;
end
1.3 理解要点
上述写的assign语句、实例元件语句、always块语句都是并行执行的,如果把它们放在一个模块里,是并行执行的,与他们的先后顺序无关。然而在always语句中,逻辑是按照指定的顺序执行的。always块中的语句称为“顺序语句”,因为它们是顺序执行的。所以“always“块也称为过程块。请注意,两个或者多个always块语句是并行执行的,它们内部的语句是顺序执行的,否则像if-else-if语句就没有任何意义。
1.4 要点总结
- 在Verilog模块中所有过程块(如initial块、always块)、连续赋值语句、实例引用都是并行的。
- 它们表示的是一种通过变量名互相连接的关系。
- 在同一模块中这三者出现的先后顺序没有关系。
- 只有连续赋值语句assign和实例引用语句可以独立于过程块而存在于模块的功能定义部分
以上的内容是和以往学习的C语言最大的不同,C语言是顺序执行的语句,这就是二者之间的差异。
2. 数据类型机器常量和变量
Verilog HDL中有19种数据类型,数据类型是用来表示数字电路硬件中的数据存储和传送元素的。其中像large型、medium型、scalared型、time型、small型、tri型、trio型、tril型、vectored型这14种数据类型中,除了time型,其他都和基本逻辑单元建库有关,与系统设计没有很大关系。这里只介绍常见的四种基本数据类型。
四种基本数据类型:
- reg型
- wire型
- integer型
- parameter型
2.1 常量
1. 数字
1. 整数
数字表达方式:
2. x和z值
在数字电路中,x代表不定值,z代表高阻态(可以看成是开路状态,管脚悬空)。一个x可以用来定义十六进制数的四位二进制数的状态,八进制数的三位二进制数的一位。z的表示方式同x类似。z还有一种表达方式,是可以写作?。在使用case表达式时建议使用这种写法,以提高程序的可读性。见下例:
对于0、x、z可以表示十进制数的全部位,赋全0、全x或者全z可采用'b0、'bx、‘bz的方式;顺带说道赋值,赋全1可采用赋~0或赋-1的方式比较简洁。
3. 负数
一个数字可以被定义位负数,只需要在位宽表达式前面加一个减号,减号必须写在数字定义表达式的最前面。注意减号不可以放在位宽和进制之间也不可以放在进制和具体的数之间。见下例:
4. 下划线
下划线可以用来分隔开数的表达式以提高程序的可读性。但不可以用在位宽和进制处,只能用在具体的数字之间。见下例:
2. 2 参数(parameter)型
看着类似的宏定义!!不太一样,可以使用#()将参数传递到module里面。
在Verilog HDL中用parameter来定义常量,parameter型数据是一种常数型的数据,其说明格式如下:
模块间传递参数
module Decode(A,F);
parameter Width=1, Polarity=1;
……………
endmodule
module Top;
wire[3:0] A4;
wire[4:0] A5;
wire[15:0] F16;
wire[31:0] F32;
Decode #(4,0) D1(A4,F16);//其中的#(4,0)向实例化的Decode模块内部传递两个参数值
Decode #(5) D2(A5,F32);//其中的#(5)向实例化的Decode模块内部传递一个参数值
endmodule
defparam语句在一个模块中改变另一个模块的参数;
其中,在模块实例引用中也可以修改parameter参数值
module Decoder(A,F);
parameter width=1,polarity=1;
...........
endmodule
module top(P,Q)
Decoder #(.width(8))
decoder(
.A(P);
.F(Q);
)
endmodlue
2.3 变量
1. wire型
wire型数据常用来表示用于以assign关键字指定的组合逻辑信号。Verilog程序模块中输入输出信号类型缺省时自动定义为wire型。wire型信号可以用作任何方程式的输入,也可以用作"assign"语句或实例元件的输出。
wire型信号的格式同reg型信号的很类似,其格式如下:
wire [n-1:0] 数据名1,数据名2,...,数据名i //共有i条总线,每条总线内有n条线路
wire [n:1] 数据名1,数据名2,...,数据名i;
例子:
wire a; //定义了一个一位的wire型数据
wire [7:0] b; //定义了一个八位的wire型数据
wire [4:1] c, d; //定义了二个四位的wire型数据
2. reg型
寄存器是数据存储单元的抽象,reg类型数据的缺省初始值为不定值x。reg型数据常用来表示用于always模块内的指定信号,常代表触发器,通常,在设计中要由always块通过使用行为描述语句来表达逻辑关系。在always块内被赋值的每个信号都必须定义成reg型。
reg型数据的格式如下:
reg [n-1:0] 数据名1,数据名2,...数据名i;
reg [n;1] 数据名1,数据名2,...数据名i;
例子
reg rega; //定义了一个一位的名为rega的reg型数据
reg [3:0] regb; //定义了一个四位的名为regb的reg型数据
reg [4:1] regc, regd; //定义了两个四位的名为regc和regd的reg型数据
reg型数据的默认初始值是一个不定值x,reg型数据可以被赋值正值,也可以赋值为负值,当一个reg型数据是一个表达式中的操作数是,它的值被当作是无符号值,即正值。例如,当一个四位的寄存器用作表达式中的操作数时,如果开始寄存器被赋以值-1,则在表达式中进行运算时,被当作为+15.
3. memory型
Verilog HDL通过对reg型变量建立数组来对存储器建模,可以描述RAM型存储器,ROM存储器和reg文件。数组中的每一个单元通过一个数组索引进行寻址。在Verilog语言中没有多维数组的存在。memory型数据是通过扩展reg型数据的地址范围来生成的。其形式如下:
reg [n-1:0] 存储器名 [m-1:0];
reg [n:1] 存储器名 [m:1];
在这里,reg[n-1:0]定义了存储器中每个存储单元的大小,即该存储单元是一个n位的寄存器,存储器名后的[m-1:0]或[m;1]则定义了该存储器中有多少个这样的寄存器;最后用分号结束定义语句。下面举例说明:
reg [7:0] mema [255:0];
上面定义了一个名为mema的存储器,该存储器有256个8位寄存器。该存储器的地址范围是0到255.
注意:对存储器进行地址索引的表达式必须是常数表达式。
另外,在同一个数据类型声明的语句里,可以同时定义存储器和reg型数据。
parameter wordsize = 16,
memsize = 256;
reg [wordsize-1:0] mem[memsize-1:0],writereg,readreg;
尽管memory型数据和reg型数据的定义格式很相似,但要注意其不同之处。如一个由n个1位寄存器构成的存储器组是不同于一个n位的寄存器的,但下例:
reg [n-1:0] rega; //一个n位的寄存器
reg mema[n-1:0]; //一个由n个一位寄存器的存储器组
一个n位的寄存器可以在一条赋值语句里进行赋值,而一个完整的存储器不行。见下例:
rega = 0; //合法赋值语句
mema = 0; //非法赋值语句
如果想对memory中的 存储单元进行读写操作,必须指定该单元在存储器中的地址。下面的写法是正确的:
mema[3] = 0; //给memory中的第3个存储单元赋值为0
运算符及表达式
基本的算术运算符
算术运算符:+、-、*、/、%
%(求模运算符,要求两边的数据均为整型数据,如7%3的值为1)
在进行整数除法运算时,结果值要略去小数部分,只取整数部分;而进行取模运算时,结果的值的符号位采用模运算式里第一个操作数的符号位。
注意:在进行算术运算操作时,如果某个操作数有不确定的值x,则整个结果也为不确定值x。
位运算符: ~、|、^、&、^~
说明:(1)位运算中除了~是单目运算符之外,其他均为双目运算符,即要求运算符两侧各有一个操作数。
(2)位运算符中的双目运算符要求对两个操作数的相应位进行运算操作。
取反~:
按位与&:
按位或|
按位异或^(XOR) ;
按位同或^~(异或非)
不同长度的数据进行位运算,自动将两者按右端对齐,位数少的操作数会在相应的高位用0填满,使得两个操作数按位进行操作。
赋值运算符:= <=
关系运算符:< > <= >=
逻辑运算符:&& || !
逻辑运算符中的”&&“和”||“的优先级低于关系运算符,”!“高于算术运算符,通过括号这些优先级可以不关注。
条件运算符:?:
拼接运算符:{}
位拼接可以用重复法来简化表达式:{4{w}} //等同于{w,w,w,w}
位拼接还可以用嵌套的方式来表达,{b,{3{a,b}}} //等同于{b,a,b,a,b,a,b}
等式运算符:==(等于)、!=(不等于)、===(等于)、!==(不等于)
注意:求反号、双等号、三个等号之间不能有空格。
这4个双目运算符,它有两个操作数。”==“和”!=“称为逻辑等式运算符,其结果由两个操作数的值决定。由于操作数中某些位可能是不定值x和高阻值z,结果可能为不定值x。而”===”和“!==”运算符则不同,它们在对操作数进行比较时,对某些位的不定值x和高阻值z也进行比较,两个操作数必须完全一致,其结果才是1,否则为0.
===和!==运算符通常用于case表达式的判断,所以又称为“case等式运算符”。这4个等式运算符的优先级级别时相同的,下面列出了运算符的真值表。
case等式运算符:
逻辑等式运算符
举个例子说明上述二者的差异:
if(A == 1'bx) $display("Aisx"); //当A等于x时,这个语句不执行
if(A === 1'bx) $display("Aisx"); //当A等于x时,这个语句执行
移位运算符
a>>n或a<<n
a代表要进行移位的操作数,n代表要移几位,这两种移位运算符都用0来填补移出的空位。
进行移位运算时应注意移位前后变量的位数,如下例:
4'b1001 << 1 = 5'b10010;
4'b1001 <<2 = 6'b100100;
1<<6 = 32'b1000000;
4'b1001 >>4 = 4'b0000;
4'b1001 >> 1 = 4'b0100;
缩减运算符
reg [3:0] B;
reg C;
C = &B;
//相当于
C = (B[0]&B[1]&B[2]&B[3]);
小结
- 在Verilog模块中所有的过程块(如initial、always块)、连续赋值语句、实例引用都是并行的。
- 它们表示的是一种通过变量名互相连接的关系。
- 在同一模块中各个过程块,各条连续赋值语句和各条实例引用语句这三者出现的先后顺序没有关系。
- 只有连续赋值语句(即用关键词assign引出的语句)和实例引用语句(即用已定义的模块名引出的语句),可以独立于过程块为存在于模块的功能定义部分。
- 被实例引用的模块,其端口可以通过不同名的连线或寄存器类型变量连接到别的模块相应的输出、输入信号端。
- 在always模块内被赋值的每个信号都必须被定义成reg型。
赋值语句和块语句
赋值语句
在Verilog HDL语言中,信号赋值有两种方式:
1.非阻塞(Non-Blocking)赋值方式(如b <= a)
(1)在语句块中,上面语句所赋的变量值不能立即就为下面的语句所用;
(2)块结束后,才能完成这次赋值操作,而所赋的变量值是上一次赋值得到的;
(3)在编写可综合的时序逻辑模块时,这是最常用的赋值方法
个人总结:非阻塞赋值就是各个赋值语句是并行执行的,可以理解为同时把右边要赋的值计算后赋值给左边
注意:非阻塞赋值<=与小于等于<=看起来是一样的,但意义完全不同,小于等于是关系运算符,非阻塞赋值是用于赋值操作。
2.阻塞(blocking)式赋值(如b=a)
(1)赋值语句执行完后,块才结束
(2)b的值在赋值语句执行完后立刻就改变的;
(3)在时序逻辑中使用时,可能会产生意想不到的结果。
个人总结:阻塞式赋值就和C语言里的赋值一样,赋值完之后值立马就改变,因为后一个赋值语句要等前一个赋值语句完成后,才会执行所以是受到它的阻塞,是阻塞式赋值。
采用上述的非阻塞式赋值,赋值是在always块结束后执行的,c应该为原来b的值。
块语句
verilog主要的模块之间都是并行执行的,例如各个always之间、always与assign之间、assign之间,如果你在一个always中要对a赋值,而在另一个always中要使用a的值,这时候就要注意了,两者并行的,处理先后不能确定,所以是不允许在多个always中对同一个变量进行赋值操作。
块语句有两种,一种是begin_end语句,通常用来标识顺序执行的语句,用它来标识的块称为顺序块。一种是fork_join语句,通常用来标识并行执行的语句, 用它来标识的块称为并行块。
-
顺序块
这里面的内容都是顺序执行的,比如b=a; c=b,先执行一条,再执行下一条,那就是c=a了如果里面有两组if/else,就是先执行前一组,再执行后一组。但是如果是非阻塞,那就要特殊对待,多个非阻塞赋值是在一个块结束时一起执行的,比如b<=a; c<=b,那就跟之前不同了,当执行c<=b 时b还没有变化成a的值,因此这个赋值的结果是b被赋值前的值,这两条语句其实是独立的、并行的。好处是放得先后顺序没关系,只要在一个块内,随便写。
顺序块有以下特点:
- 块内的语句是按顺序执行的,即只有上面一条语句执行完后下面的语句才能执行。
- 每条语句的延迟时间是相对于前一条语句的仿真时间而言的。
- 直到最后一条语句执行完,程序流程控制才跳出该语句块。
顺序块的格式如下:
begin
//语句1;
//语句2;
//......
//语句n;
end
//或者
begin:块名
//块内声明语句
//语句1;
//语句2;
//......
//语句n;
end
2. 并行块
并行块有以下四个特点:
1、块内语句是同时执行的,即程序流程控制一进入到该并行块,块内语句则开始同时并行地执行。
2、块内每条语句的延迟时间是相对于程序流程控制进入到该块内时的仿真时间的。
3、延迟时间是用来给赋值语句提供执行时序的。
4、当按时间时序排序在最后的语句执行完后或一个disable语句执行时,程序流程控制跳出该程序块。
并行块的格式如下:
fork//fork真有意思,分叉,fork()函数生成子进程也是分叉!!join,是又汇合了啊。。
//语句1;
//语句2;
//......
//语句n;
join
//或者
fork:块名
//块内声明语句
//语句1;
//语句2;
//......
//语句n;
join