Verilog基础知识总结
上一篇总结包括逻辑值、进制表示、数据类型、关键字和运算符,接下来总结内容包括:变量赋值、条件语句、循环语句和结构说明语句。所有知识点仅代表个人理解,若有误,欢迎指教。上一篇地址:Verilog基础知识(1)
赋值语句
在verilog中的赋值主要包括:过程赋值语句和持续赋值语句
持续赋值语句
持续赋值语句是 Verilog
数据流建模的基本语句。针对的数据类型主要是标量线网类型wire
和向量线网类型wire[n-1:0]
,assign
为关键词,任何已经声明 wire
变量的连续赋值语句都是以assign
开头。
- 显示持续赋值语句
<net_declaration><range><name>;
assign #<delay><name>= Assignment expression;
net_declaration
:连线型变量类型
range
:变量位宽,指明了变量数据类型的宽度,格式为[msb:lab]
,缺省为1
位。
delay
:延时量,这一项是可选的。
例:assign y = m|n; assign #(3, 2, 4)c = a&b;
- 隐式持续赋值语句
-
<net_declaration><drive_strength><range>#<delay><name>=Assignment expression;
drive_strength
:(赋值驱动强度)是可选的,只能在“隐式连续赋值语句”格式中得到指定。它用来对连线型变量受到的驱动强度进行指定。例如:wire (weak0,strong1) out=in1&in2;
隐式赋值就是在声明变量时赋初值。
- 持续赋值语句注意点
- 赋值目标只能是线网类型(wire) ;
- 在连续赋值中,只要赋值语句右边表达式任何一个变量有变化,表达式立即被计算,计算的结果立即赋给左边信号(若没有定义延时量);
- 连续赋值语句不能出现在过程块中。
- 多个连续赋值语句之间是并行语句,因此与位置顺序无关。
- 连续赋值语句中的延时具有硬件电路中惯性延时的特性,任何小于其延时的信号变化脉冲都将被滤除掉,不会体现在输出端口上。
过程赋值语句
过程性赋值是在 initial
或 always
语句块里的赋值,赋值对象是寄存器、整数、实数等类型。这些变量在被赋值后,其值将保持不变,直到重新被赋予新值。
连续性赋值总是处于激活状态,任何操作数的改变都会影响表达式的结果;过程赋值只有在语句执行的时候,才会起作用。
Verilog
过程赋值包括 2
种语句:阻塞赋值
与非阻塞赋值
。
阻塞赋值
阻塞赋值属于顺序执行,即下一条语句执行前,当前语句一定会执行完毕。阻塞赋值语句使用等号 =
作为赋值符。
例:
module block1(din,clk,out1 ,out2);
input din,clk;
output out1,out2;
reg out1,out2;
always@(posedge clk) begin
out1=din;
out2=out1;
end
endmodule
上述程序综合结果为:
非阻塞赋值
非阻塞赋值属于并行执行语句,即下一条语句的执行和当前语句的执行是同时进行的,它不会阻塞位于同一个语句块中后面语句的执行。非阻塞赋值语句使用小于等于号 <=
作为赋值符。
例:
module block2(din,clk,out1 ,out2);
input din,clk;
output out1,out2;
reg out1,out2;
always@(posedge clk) begin
out1<=din;
out2<=out1;
end
endmodule
上述程序综合结果为:
条件语句
Verilog
的条件分支语句有两种: if
条件语句和case
条件分支语句。
if 条件语句
if
条件语句就是判断所给的条件是否满足,然后根据判断的结果来确定下一步的操作。
-
形式1
if(条件表达式) 语句块;
-
形式2
if(条件表达式) 语句块1; else 语句块2;
-
形式3
if(条件表达式1) 语句块1; else if(条件表达式2) 语句块2; ........ ........ else if(条件表达式i) 语句块i; else 语句块n;
-
形式4:if 语句中嵌套if 语句
if(条件表达式1) if(条件表达式2) //内嵌的if语句 语句块1; else 语句块2; else if(条件表达式3) //内嵌的if语句 语句块3; else 语句块4;
case 条件分支语句
相对于if
语句只有两个分支而言,case
语句是一种可实现多路分文选择控制的语句,比if-else
条件语句显得更为万便和直观。一般,case
语句多用于多条件译码电路设计。
case
语句的语法格式是:
case(控制表达式)
值1:语句块1
值2:语句块2
........
值n:语句块n
default:语句块n+1
endcase
真值表:
case | 0 | 1 | x | z |
---|---|---|---|---|
0 | 1 | 0 | 0 | 0 |
1 | 0 | 1 | 0 | 0 |
x | 0 | 0 | 1 | 0 |
z | 0 | 0 | 0 | 1 |
例:BCD数码管译码
module BCD_decoder(out, in);
output[6:0]out;
input[3:0]in;
reg [6:0]out;
always@(in) begin
case(in)
4'd0:out=7'b1111110;
4'd1:out=7'b0110000;
4'd2:out=7'b1101101;
4'd3:out=7'b1111001;
4'd4:out=7'b0110011;
4'd5:out=7'b1011011;
4'd6:out=7'b1011111;
4'd7:out=7'b1110000;
4'd8:out=7'b1111111;
4'd9:out=7'b1111011;
default:out=7'bx;
endcase
end
endmodule
case
语句注意事项:
- 值
1
到值n
之间必须各不相同,一旦判断到与某值相同并执行相应语句块后,case
语句的执行便结束; - 如果某几个连续排列的值项执行的是同一条语句,则这几个值项间可用逗号相隔,而将语句放在这几个值项的最后一个中;
default
选项相当于if-else
语句中的else
部分,可依据需要用或者不用,当前面已经列出了敏感表达式的所有可能值,则default
可以省略;case
语句的所有表达式的值的位宽必须相等,只有这样控制表达式和分支表达式才能进行对应位的比较。- 在使用
case
语句时,应包含所有状态,如果没包含全,那么缺省项必须写,否则将产生锁存器,这在同步时序电路设计中是不允许的。
除了case
分支语句以外,还有casez
、casex
这两种功能类似的条件分支语句:
-
casez
:用来处理不考虑高阻值z
的比较过程。casez 0 1 x z 0 1 0 0 1 1 0 1 0 1 x 0 0 1 1 z 1 1 1 1 -
casex
:将高阻值z
和不定值x
视为不关心的情况。casex 0 1 x z 0 1 0 1 1 1 0 1 1 1 x 1 1 1 1 z 1 1 1 1
循环语句
Verilog
循环语句有4
种类型,分别是while
,for
,repeat
,和forever
循环。循环语句只能在always
或initial
块中使用,但可以包含延迟表达式。
forever 循环语句
- 关键字
forever
所引导的循环语句表示永久循环。在永久循环中不包含任何条件表达式,只执行无限的循环,直到遇到系统任务$finish
为止。如果需要从forever
循环中退出,则可以使用disable
语句。必须写在initial
语句块中。 - 例:用
forever
语句产生时钟信号module forever _tb; reg clock; initial begin clock = 0; forever #50 clock=~clock; //产生周期为100的时钟信号 end endmodule
repeat 循环语句
- 关键字
repeat
所引导的循环语句表示执行固定次数的循环。 - 其语法格式是:
repeat(循环次数表达式)
语句或语句块(循环体);
repeat
循环的次数必须是一个常量、变量或信号。如果循环次数是变量信号,则循环次数是开始执行repeat
循环时变量信号的值。即便执行期间,循环次数代表的变量信号值发生了变化,repeat
执行次数也不会改变。- 例:使用
repeat
循环语句产生固定周期数时钟信号module repeat _tb; reg clock; initial begin clock = 0; repeat(8) #50 clock=~clock; //产生8个周期的时钟信号 end endmodule
while 循环语句
- 关键字
while
所引导的循环语句表示的是一种条件循环
。 while
语句根据条件表达式的真假来确定循环体的执行,当指定的条件表达式为真时才会重复执行循环体,否则就不执行循环体。- 语法格式:
while(条件表达式) 语句或语句块;
- 例:
module while_tb; reg [3:0] counter ; initial begin counter = 'b0 ; while (counter<=10) begin #10 ; counter = counter + 1'b1 ; //counter 执行了 11 次 end end endmodule
for 循环语句
- 关键字
for
所要引导的循环语句也表示一种条件循环
,只有在指定的条件表达式成立时才进行循环。 - 语法格式是:
for(循环变量赋初值;循环结束条件;循环变量增值) 语句块;
- 例:
module for_tb; integer i ; reg [3:0] counter2 ; initial begin counter2 = 'b0 ; for (i=0; i<=10; i=i+1) begin #10 ; counter2 = counter2 + 1'b1 ; end end endmodule
- 注:循环变量增值,i = i + 1 不能像 C 语言那样写成 i++ 的形式,i = i -1 也不能写成 i – 的形式。
结构说明语句
该部分主要介绍initial
语句、always
语句、任务task
和函数function
。
initial 语句
-
initial
语句从0
时刻开始执行,只执行一次,多个initial
块之间是相互独立的。 -
如果
initial
块内包含多个语句,需要使用关键字begin
和end
组成一个块语句。 -
如果
initial
块内只要一条语句,关键字begin
和end
可使用也可不使用。 -
initial
理论上来讲是不可综合的,多用于初始化、信号检测等。 -
语法格式:
initial begin 语句1; 语句2; ..... 语句n; end
-
例:用
initial
过程语句对变量A,B,C
进行赋值module initial_tb1; reg A,B,C; initial begin A=0;B=1;C=0; #100 A=1;B=0; #100 A=0;C=1; #100 B=1; #100 B=0;C=0; end endmodule
always 语句
-
与
initial
语句相反,always
语句块的触发状态是一直存在的,只要满足always
后面的敏感事件列表,就执行过程块。 -
always
语句块从0
时刻开始执行其中的行为语句;当执行完最后一条语句后,便再次执行语句块中的第一条语句,如此循环反复。 -
语法格式:
always@(<敏感事件列表>) 语句块;
- 敏感事件可以时电平信号,也可以是时钟边沿信号;可以有一个信号,也可以有多个信号,当出现多个信号时中间用关键字
or
连接。 - 当敏感信号较多时,可以采用
*
代替所有的敏感信号。
- 敏感事件可以时电平信号,也可以是时钟边沿信号;可以有一个信号,也可以有多个信号,当出现多个信号时中间用关键字
-
例:4选1数据选择器
module mux4_1(out,in0,in1 ,in2,in3,sel); output out; input in0,in1,in2,in3; input[1:0] sel; reg out; //被赋值信号定义为“reg”类型 always @( in0 or in1 or in2 or in3 or sel) //敏感信号列表 case(sel) 2'b00: out=in0; 2'b01: out=in1; 2'b10: out=in2; 2'b11: out=in3; default: out=2'bx; endcase endmodule
注:
-
在信号定义形式方面,无论是对时序逻辑还是组合逻辑描述,
Verilog
要求在过程语句(initial
和always
)中,被赋值信号必须定义为reg
类型; -
采用
always
对组合电路进行描述时,作为输入的全部信号需要列入敏感信号列表; -
采用
always
对时序电路进行描述时,需要把时间信号和部分输入信号列入敏感信号列表。应当注意的是,不同的敏感事件列表会产生不同的电路形式。
function 语句
-
函数
function
只能在模块中定义,位置任意,并在模块的任何地方引用,作用范围也局限于此模块。函数主要有以下几个特点:- 不含有任何延迟、时序或时序控制逻辑
- 至少有一个输入变量
- 只有一个返回值,且没有输出
- 不含有非阻塞赋值语句
- 函数可以调用其他函数,但是不能调用任务
-
函数的声明语法:
function [range-1:0] function_id ; input_declaration ; other_declaration ; procedural_statement ; endfunction
-
函数在声明时,会隐式的声明一个宽度为
range
、名字为function_id
的寄存器变量,函数的返回值通过这个变量进行传递。 -
当该寄存器变量没有指定位宽时,默认位宽为
1
。 -
函数通过指明函数名与输入变量进行调用。函数结束时,返回值被传递到调用处。
-
-
例:
function [N-1:0] data_rvs ; input [N-1:0] data_in ; parameter MASK = 32'h3 ; integer k ; begin for(k=0; k<N; k=k+1) begin data_rvs[N-k-1] = data_in[k] ; end end endfunction //调用函数形式 input [N-1:0] a; //为函数准备参数 reg [N-1:0] b_temp; //定义变量接收函数返回结果 b_temp = data_rvs(a); //调用函数
task 语句
-
任务可以在模块中任意位置定义,并在模块内任意位置引用,作用范围也局限于此模块。
-
模块内子程序出现下面任意一个条件时,则必须使用任务而不能使用函数。
- 子程序中包含时序控制逻辑,例如延迟,事件控制等
- 没有输入变量
- 没有输出或输出端的数量大于
1
-
任务声明格式:
task <automatic> task_id; //<automatic>为可选项 port_declaration ; procedural_statement ; endtask
- 任务中使用关键字
input
、output
和inout
对端口进行声明。input
、inout
型端口将变量从任务外部传递到内部,output
、inout
型端口将任务执行完毕时的结果传回到外部。 - 进行任务的逻辑设计时,可以把
input
声明的端口变量看做wire
型,把output
声明的端口变量看做reg
型。但是不需要用reg
对output
端口再次说明。
- 任务中使用关键字
-
例:
task xor_oper_iner; input [N-1:0] numa; input [N-1:0] numb; output [N-1:0] numco ; //无需再注明 reg 类型 #3 numco = numa ^ numb ; //不用assign,因为输出默认是reg endtask
-
任务调用:
task_id(input1, input2, …,outpu1, output2, …);
-
任务调用时,端口必须按顺序对应。
-
输入端连接的模块内信号可以是
wire
型,也可以是reg
型。输出端连接的模块内信号要求一定是reg
型。
-
task和function的区别
function
需要在一个单位仿真时间内完成,而task中可以包含时间控制的命令,因此#
,@
,wait
等语句都不能出现在function
中;function
不能调用task
,但是task
可以调用function
;function
至少需要一个input
端口,同时不能出现output
和inout
端口,task
没有这个要求;function
需要有返回值,task
可以没有返回值;function
中不能使用非阻塞赋值。