Verilog 入门(六)行为建模

过程结构

下述两种语句是为一个设计的行为建模的主要机制:

  1. initial 语句
  2. always 语句

一个模块中可以包含任意多个 initialalways 语句。这些语句相互并行执行,即这些语句的执行顺序与其在模块中的顺序无关。一个 initialalways 语句的执行产生一个单独的控制流,所有的 initialalways 语句在 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 的过程语句基于事件执行。有两种类型的事件控制方式:

  1. 边沿触发事件控制
  2. 电平敏感事件控制

边沿触发事件控制如下:

@ event procedural_statement

如下例所示:

@(posedge clock)
curr_state = next_state

带有事件控制的进程或过程语句的执行,须等到指定事件发生。上例中,如果 clock 信号从低电平变为高电平(正沿),就执行赋值语句;否则进程被挂起,直到 clock 信号产生下一个正跳边沿。

在电平敏感事件控制中,进程语句或进程中的过程语句一直延迟到条件变为真后才执行。电平敏感事件控制以如下形式给出:

wait(Condition)
  procedural_statement

过程语句只有在条件为真时才执行,否则过程语句一直等待到条件为真。如果执行到该语句时条件已经为真,那么过程语句立即执行。在上面的表示形式中,过程语句是可选的。

语句块

语句块提供将两条或更多条语句组合成语法结构上相当于一条语句的机制。在 Verilog 中有两类语句块,即:

  1. 顺序语句块(begin...end):语句块中的语句按给定次序顺序执行。
  2. 并行语句块(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 个赋值,以此类推。

过程性赋值

过程性赋值是在 initialalways 语句内的赋值,它只能对寄存器数据类型的变量赋值

过程性赋值分两类:

  1. 阻塞性过程赋值
  2. 非阻塞性过程赋值

阻塞性过程赋值

赋值操作符是 = 的过程赋值是阻塞性过程赋值。阻塞性过程赋值在其后所有语句执行前执行,即在下一语句执行前该赋值语句完成执行。

非阻塞性过程赋值

在非阻塞性过程赋值中,使用赋值符号 <=

在非阻塞性过程赋值中,对目标的赋值是非阻塞的(因为时延),但可预定在将来某个时间步发生(根据时延;如果是 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 与最近的没有 elseif 相关联。

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 中有四类循环语句:

  1. forever 循环
  2. repeat 循环
  3. while 循环
  4. 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

这两个进程通过寄存器 ReadyAck 的交互握手协议如下图所示:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/myDarling_/article/details/134734958