文章目录
过程结构
下述两种语句是为一个设计的行为建模的主要机制:
initial
语句always
语句
一个模块中可以包含任意多个 initial
或 always
语句。这些语句相互并行执行,即这些语句的执行顺序与其在模块中的顺序无关。一个 initial
或 always
语句的执行产生一个单独的控制流,所有的 initial
和 always
语句在 0 时刻开始并行执行。
initial
语句
initial
语句只执行一次。initial
语句在模拟开始时执行,即在 0 时刻开始执行。语法如下:
initial
[timing control] procedural_statement
procedural_statement
是下列语句之一:
- procedural_assignment (blocking or non-blocking)
- procedural_continuous_assignment
- conditional_statement
- case_statement
- loop_statament
- wait_statement
- disable_statement
- event_trigger
- sequential_block
- parallel_block
- task_enable
顺序过程(begin...end
)最常使用在进程语句中。这里的时序控制可以是时延控制,即等待一个确定的时间;或事件控制,即等待确定的事件发生或某一特定的条件为真。
always
语句
与 initial
语句相反,always
语句重复执行。与 initial
语句类似,always
语句语法如下:
always
[timing control] procedural_statement
例如,产生时钟周期为 10 的波形:
always
#5 clk = ~ clk;
下例是由事件控制的顺序过程的 always
语句:
reg[0:5] InstrReg;
reg[3:0] Accum;
wire ExecuteCycle;
always@(ExecuteCycle)
begin
case(InstrReg[0:1])
2'b00: Store(Accum, InstrReg[2:5]);
2'b11: Load(Accum, InstrReg[2:5]);
2'b01: Jump(InstrReg[2:5]);
2'b10:;
endcase
end
// Store、Load 和 Jump 是在别处定义的用户自定义的任务
事件控制
在事件控制中,always
的过程语句基于事件执行。有两种类型的事件控制方式:
- 边沿触发事件控制
- 电平敏感事件控制
边沿触发事件控制如下:
@ event procedural_statement
如下例所示:
@(posedge clock)
curr_state = next_state
带有事件控制的进程或过程语句的执行,须等到指定事件发生。上例中,如果 clock
信号从低电平变为高电平(正沿),就执行赋值语句;否则进程被挂起,直到 clock
信号产生下一个正跳边沿。
在电平敏感事件控制中,进程语句或进程中的过程语句一直延迟到条件变为真后才执行。电平敏感事件控制以如下形式给出:
wait(Condition)
procedural_statement
过程语句只有在条件为真时才执行,否则过程语句一直等待到条件为真。如果执行到该语句时条件已经为真,那么过程语句立即执行。在上面的表示形式中,过程语句是可选的。
语句块
语句块提供将两条或更多条语句组合成语法结构上相当于一条语句的机制。在 Verilog 中有两类语句块,即:
- 顺序语句块(
begin...end
):语句块中的语句按给定次序顺序执行。 - 并行语句块(
fork...join
):语句块中的语句并行执行。
顺序语句块
顺序语句块中的语句按顺序方式执行。每条语句中的时延值与其前面的语句执行的模拟时间相关。一旦顺序语句块执行结束,跟随顺序语句块过程的下一条语句继续执行。顺序语句块的语法如下:
begin
[:block_id{
declarations}]
procedural_statement(s)
end
例如
begin
#2 Stream = 1;
#5 Stream = 0;
#3 Stream = 1;
#4 Stream = 0;
#2 Stream = 1;
#5 Stream = 0;
end
假定顺序语句块在第 10 个时间单位开始执行。两个时间单位后第 1 条语句执行,即第 12 个时间单位。此执行完成后,下 1 条语句在第 17 个时间单位执行(延迟 5 个时间单位)。然后下 1 条语句在第 20 个时间单位执行,以此类推。
并行语句块
并行语句块中的各语句并行执行。并行语句块内的各条语句指定的时延值都与语句块开始执行的时间相关。当并行语句块中最后的动作执行完成时(最后的动作并不一定是最后的语句),顺序语句块的语句继续执行。换一种说法就是并行语句块内的所有语句必须在控制转出语句块前完成执行。例如
fork
#2 Stream = 1;
#7 Stream = 0;
#10 Stream = 1;
#14 Stream = 0;
#16 Stream = 1;
#21 Stream = 0;
join
如果并行语句块在第 10 个时间单位开始执行,所有的语句并行执行并且所有的时延都是相对于时刻 10 的。例如,第 3 个赋值在第 20 个时间单位执行,并在第 26 个时间单位执行第 5 个赋值,以此类推。
过程性赋值
过程性赋值是在 initial
或 always
语句内的赋值,它只能对寄存器数据类型的变量赋值。
过程性赋值分两类:
- 阻塞性过程赋值
- 非阻塞性过程赋值
阻塞性过程赋值
赋值操作符是 =
的过程赋值是阻塞性过程赋值。阻塞性过程赋值在其后所有语句执行前执行,即在下一语句执行前该赋值语句完成执行。
非阻塞性过程赋值
在非阻塞性过程赋值中,使用赋值符号 <=
。
在非阻塞性过程赋值中,对目标的赋值是非阻塞的(因为时延),但可预定在将来某个时间步发生(根据时延;如果是 0 时延,那么在当前时间步结束)。
begin
Load <= 32;
RegA <= Load;
RegB <= Store;
end
当非阻塞性过程赋值被执行时,计算右端表达式,右端值被赋于左端目标,并继续执行下一条语句。在上面的例子中,我们假设顺序语句块在时刻 10 执行。第一条语句促使 Load
在第 10 个时间单位结束时被赋值为 32;然后执行第 2 条语句, Load
的值不变(注意时间还没有前进,并且第 1 个赋值还没有被赋新值),RegA
的赋值被预定为在第 10 个时间步结束时。在所有的事件在第 10 个时间单位发生后,完成对左端目标的所有预定赋值。
下面是同时使用阻塞性和非阻塞性过程赋值的实例,注意它们的区别。
reg[0:2] Q_State;
initial begin
Q_state = 3'b011;
Q_state <= 3'b100;
$display("Current value of Q_State is %b", Q_State);
#5;
$display("The delayed value of Q_State is %b", Q_State);
end
执行 initial
语句产生如下结果:
Current value of Q_State is 011
The delayed value of Q_State is 100
if
语句
if
语句语法如下:
if(condition_1)
procedural_statement_1
{
else if(condition_2)
procedural_statement_2}
{
else
procedural_statement_3}
注意条件表达式必须总是被括起来,如果使用 if-if-else
格式,那么可能会有二义性,如下例所示:
if(clk)
if(Reset)
Q = 0;
else
Q = D;
问题是最后一个 else
属于哪一个 if
?Verilog 中会将 else
与最近的没有 else
的 if
相关联。
case
语句
case
语句是一个多路条件分支形式,其语法如下:
case(case_expr)
case_item_expr{
, case_item_expr}: procedural_statement
...
...
[default: procedural_statement]
endcase
case
语句一实例如下:
module ALU(A, B, OpCode, Z);
input[3:0] A, B;
input[1:2] OpCode;
output[7:0] Z;
reg[7:0] Z;
parameter
ADD_INSTR = 2'b10,
SUB_INSTR = 2'b11,
MUL_INSTR = 2'b01,
DIV_INSTR = 2'b00;
always@(A or B or OpCode)
case(OpCode)
ADD_INSTR: Z = A + B;
SUB_INSTR: Z = A - B;
MUL_INSTR: Z = A * B;
DIV_INSTR: Z = A / B;
endcase
endmodule
循环语句
Verilog 中有四类循环语句:
forever
循环repeat
循环while
循环for
循环
forever
循环
forever
循环语法如下:
forever
procedural_statement
此循环语句连续执行过程语句。因此为跳出这样的循环,中止语句可以与过程语句共同使用。同时,在过程语句中必须使用某种形式的时序控制,否则,forever
循环将在 0 时延后永远循环下去。
initial begin
clock = 0;
#5 forever
#10 clock = ~clock;
end
这一实例产生时钟波形;时钟首先初始化为 0,并一直保持到第 5 个时间单位。此后每隔 10 个时间单位,clock
反相一次。
repeat
循环语句
repeat
循环语句形式如下:
repeat(loop_count)
procedural_statement
这种循环语句执行指定循环次数的过程语句。如果循环计数表达式的值不确定,即为 x 或 z 时,那么循环次数按 0 处理。
while
循环语句
while
循环语句语法如下:
while(condition)
procedural_statement
此循环语句循环执行过程赋值语句直到指定的条件为假。如果表达式在开始时为假,那么过程语句便永远不会执行。
while(By > 0)
begin
Acc = Acc << 1;
By = by - 1;
end
for
循环语句
for
循环语句的形式如下:
for(initial_assignment;condition;step_assignment)
procedural_statement
一个 for
循环语句按照指定的次数重复执行过程赋值语句若干次。初始赋值 initial_assignment
给出循环变量的初始值。condition
条件表达式指定循环在什么情况下必须结束。只要条件为真,循环中的语句就执行;而 step_assignment
给出要修改的赋值,通常为增加或减少循环变量计数。
integer K;
for(K=0; K<MAX_RANGE; K=K+1)
begin
if(Abus[K] == 0)
Abus[K] = 1;
else if(Abus[K] == 1)
Abus[K] = 0;
else
$display("Abus[K] is an x or a z");
end
握手协议实例
always
语句可用于描述交互进程的行为,如有限状态机的交互。这些模块内的语句用对所有 always
语句可见的寄存器来相互通信。
考虑下面两个交互进程的实例:RX 接收器;MP 微处理器。RX 进程读取串行的输入数据。并发送 Ready
信号表明数据可被读入 MP 进程。MP 进程在将数据分配给输出后,回送一个接收信号 Ack
到 RX 进程以读取新的输入数据。两个进程的语句块流程如下图所示:
这两个交互进程的行为可用下述行为模型加以描述:
`timescale 1ns/100ps
module Interacting(Serial_In, Clk, Parallel_Out)
input Serial_In, Clk;
output[0:7] Parallel_Out;
reg[0:7] Parallel_Out;
reg Ready, Ack;
wire[0:7] Data;
`include "Read_Word.v"
always
begin:RX
Read_Word(Serial_In, Clk, Data);
// 任务 Read_Word 在每个时钟周期读取串行数据,将其转换为并行数据并存于 Data中,完成该任务需要 10ns
Ready = 1;
wait(Ack);
Ready = 0;
#40;
end:RX
always
begin:MP
#25;
Parallel_Out = Data;
Ack = 1;
#25 Ack = 0;
wait(Ready);
end:MP
endmodule
这两个进程通过寄存器 Ready
和 Ack
的交互握手协议如下图所示: