当前位置:   article > 正文

9.行为建模(Behavioral modeling)

行为建模

9.1行为模型概述
        Verilog行为模型包含控制模拟和操纵先前描述的数据类型变量的过程语句。这些语句包含在程序中。每个过程都有一个与其关联的活动流。活动开始于initial和always语句。每个initial语句和每个always语句都会启动一个单独的活动流。所有活动流都是并发的,以模拟硬件的固有并发性。9.9中正式描述了这些构造。
       initial和always语句定义的所有流都在模拟时间零点开始。initial语句执行一次,而always 语句重复执行。initial语句完成后,并且在此模拟运行期间不会再次执行。

9.2过程赋值
        如第6条所述,过程赋值用于更新reg、integer、time、real、realtime和memory数据类型。
过程赋值和连续赋值之间存在显著差异:

——过程赋值驱动nets,每当输入操作数改变值时进行计算和更新。

——过程赋值在围绕它们的过程流构造的控制下更新变量的值。

过程赋值的右侧可以是任何计算值的表达式。左侧应为从右侧接收赋值的变量。过程赋值的左侧可以采用以下形式之一:

——reg、integer、real、realtime或time数据类型:对这些数据类型之一的名称引用的赋值。
——reg、整数或时间数据类型的位选择:对单个位的赋值,使其他位保持不变。
——reg、整数或时间数据类型的部分选择:对一个或多个连续位的部分选择,使其余位保持不变。
——memory word:memory中的单个字。
——上述任何一种形式的拼接或嵌套拼接:前面四种形式中任何一种的拼接或嵌套拼接。这样的规范有效地划分了右侧表达式的结果,并将划分部分按顺序分配给拼接或嵌套拼接的各个部分。

Verilog HDL包含两种类型的程序赋值语句:

        ——阻塞过程赋值语句
        ——非阻塞过程赋值语句

阻塞和非阻塞过程赋值语句在顺序块中指定不同的过程流。

9.2.1阻塞过程赋值
        阻塞过程赋值语句应在执行顺序块中紧随其后的语句之前执行(见9.8.1)。阻塞过程赋值语句不应阻止并行块中其后语句的执行(见9.8.2)。阻塞过程赋值的语法在语法9-1中给出。

  1. blocking_assignment ::= (From A.6.2)
  2. variable_lvalue = [ delay_or_event_control ] expression
  3. delay_control ::= (From A.6.5)
  4. # delay_value
  5. | # ( mintypmax_expression )
  6. delay_or_event_control ::=
  7. delay_control
  8. | event_control
  9. | repeat ( expression ) event_control
  10. event_control ::=
  11. @ hierarchical_event_identifier
  12. | @ ( event_expression )
  13. | @*
  14. | @ (*)
  15. event_expression ::=
  16. expression
  17. | posedge expression
  18. | negedge expression
  19. | event_expression or event_expression
  20. | event_expression , event_expression
  21. variable_lvalue ::= (From A.8.5)
  22. hierarchical_variable_identifier [ { [ expression ] } [ range_expression ] ]
  23. | { variable_lvalue { , variable_lvalue } }

                                                语法9-1阻塞过程赋值的语法
        在此语法中,variable_lvalue是对过程赋值语句有效的数据类型,=是赋值运算符,delay_or_event_control是可选的赋值内定时控件。控件可以是delay_control(例如#6)或event_control(例如@(posedge clk))。表达式是应分配给左侧的右侧值。如果variable_lvalue需要计算,则应在分配内定时控制指定的时间进行计算。用于阻塞过程赋值的=赋值运算符也用于过程连续赋值和连续赋值。

9.2.2非阻塞程序分配(The nonblocking procedural assignment)

       非阻塞程序分配允许在不阻塞程序流的情况下进行分配调度。只要可以在同一时间步长内进行多个变量赋值,而不考虑顺序或相互依赖,就可以使用非阻塞过程赋值语句。非阻塞过程赋值的语法在语法9-2中给出。

  1. nonblocking_assignment ::= (From A.6.2)
  2. variable_lvalue <= [ delay_or_event_control ] expression
  3. delay_control ::= (From A.6.5)
  4. # delay_value
  5. | # ( mintypmax_expression )
  6. delay_or_event_control ::=
  7. delay_control
  8. | event_control
  9. | repeat ( expression ) event_control
  10. event_control ::=
  11. @ hierarchical_event_identifier
  12. | @ ( event_expression )
  13. | @*
  14. | @ (*)
  15. event_expression ::=
  16. expression
  17. | posedge expression
  18. | negedge expression
  19. | event_expression or event_expression
  20. | event_expression , event_expression
  21. variable_lvalue ::= (From A.8.5)
  22. hierarchical_variable_identifier [ { [ expression ] } [ range_expression ] ]
  23. | { variable_lvalue { , variable_lvalue } }

语法9-2-非阻塞赋值的语法

        在此语法中,variable_lvalue是对过程赋值语句有效的数据类型,<=是非阻塞赋值运算符,delay_or_event_control是可选的赋值内定时控件。如果variable_lvalue需要求值,则应与右侧的表达式同时求值。如果未指定定时控制,则variable_value和右侧表达式的求值顺序未定义。

        在时间步结束时,表示非阻塞赋值是在时间步中执行的最后一个分配,只有一个例外。
非阻塞赋值事件可以创建阻塞赋值事件。这些阻塞赋值事件应在计划的非阻塞事件之后处理。
与用于阻塞赋值的事件或延迟控件不同,非阻塞赋值不会阻塞过程流。非阻塞赋值计算并调度赋值,但它不会阻塞begin-end块中后续语句的执行。

  1. Blocking Example
  2. //non_block1.v
  3. module non_block1; reg a, b, c, d, e, f;
  4. //blocking assignments
  5. initial
  6. begin
  7. a = #10 1; // a will be assigned 1 at time 10
  8. b = #2 0; // b will be assigned 0 at time 12
  9. c = #4 1; // c will be assigned 1 at time 16
  10. end
  11. //non-blocking assignments
  12. initial
  13. begin
  14. d <= #10 1; // d will be assigned 1 at time 10
  15. e <= #2 0; // e will be assigned 0 at time 2
  16. f <= #4 1; // f will be assigned 1 at time 4
  17. end
  18. endmodule

        如前一个示例所示,模拟器评估并调度当前时间步结束时的分配,并可以使用非阻塞程序分配执行交换操作。

  1. Example 2
  2. //non_block1.v
  3. module non_block1;
  4. reg a, b;
  5. initial
  6. begin
  7. a = 0;
  8. b = 1;
  9. a <= b; // evaluates, schedules, and
  10. b <= a; // executes in two steps
  11. end
  12. initial
  13. begin
  14. $monitor ($time, ,"a = %b b = %b", a, b);
  15. #100 $finish;
  16. end
  17. endmodule
  18. 最后赋值结果:a=1;b=0

        模拟器评估非阻塞分配的右侧,并在当前时间步长结束时安排分配。
        在当前时间步结束时,模拟器更新每个非阻塞赋值语句的左侧。

应保留对给定变量执行不同非阻塞赋值的顺序。换言之,如果一组非阻塞分配的执行顺序明确,则非阻塞分配目的地的最终更新顺序应与执行顺序相同(如下Example 3示例)。

  1. Example 3:
  2. module multiple;
  3. reg a;
  4. initial a = 1;
  5. // The assigned value of the reg is determinate
  6. initial
  7. begin
  8. a <= #4 0; // schedules a = 0 at time 4
  9. a <= #4 1;// schedules a = 1 at time 4
  10. end // At time 4, a = 1
  11. ednmodule

        如果模拟器同时执行两个程序块,并且这些程序块包含对同一变量的非阻塞赋值运算符,则该变量的最终值是不确定的。例如,在以下示例中,reg a的值是不确定的:

  1. Example 4
  2. module multiple2;
  3. reg a;
  4. initial a = 1;
  5. initial a <= #4 0; // schedules 0 at time 4
  6. initial a <= #4 1; // schedules 1 at time 4
  7. // At time 4, a = ??
  8. // The assigned value of the reg is indeterminate endmodule

        针对同一变量的两个非阻塞赋值位于不同的块中,这一事实本身并不足以使变量的赋值顺序不确定。例如,在以下示例中确定时间周期16结束时reg a的值:

  1. Example 5:
  2. module multiple3;
  3. reg a;
  4. initial #8 a <= #8 1; // executed at time 8;
  5. // schedules an update of 1 at time 16
  6. initial #12 a <= #4 0; // executed at time 12;
  7. // schedules an update of 0 at time 16
  8. // Because it is determinate that the update of a to the value 1
  9. // is scheduled before the update of a to the value 0,
  10. // then it is determinate that a will have the value 0
  11. // at the end of time slot 16.
  12. endmodule

下面的示例显示了如何将i[0]的值分配给r1,以及如何在每次时间延迟后安排分配:

  1. module multiple4;
  2. reg r1;
  3. reg [2:0] i;
  4. initial begin
  5. // makes assignments to r1 without cancelling previous assignments
  6. for (i = 0; i <= 5; i = i+1)
  7. r1 <= # (i*10) i[0];
  8. end
  9. endmodule
  10. initial 中的for循环其实就是将for循环展开得到的,其等价于下面的表达式:
  11. i=0 , r1<=0*10 i[0];
  12. i=1 , r1<=1*10 i[0];
  13. i=2 , r1<=2*10 i[0];
  14. i=3 , r1<=3*10 i[0];
  15. i=4 , r1<=4*10 i[0];
  16. i=5 , r1<=5*10 i[0];

 9.3过程连续赋值语句

        过程连续赋值(使用关键字assign和force)是允许表达式连续驱动到变量或网络上的过程语句。这些语句的语法在语法9-3中给出。assign语句中赋值的左侧应为变量引用或变量拼接。它不应是内存字(数组引用)、位选择或变量的部分选择。相反,force语句中赋值的左侧可以是变量引用或net引用。它可以是上述任何一个的拼接。不允许矢量变量的位选择和部分选择。

  1. net_assignment ::= (From A.6.1)
  2. net_lvalue = expression
  3. procedural_continuous_assignments ::= (From A.6.2)
  4. assign variable_assignment
  5. | deassign variable_lvalue
  6. | force variable_assignment
  7. | force net_assignment
  8. | release variable_lvalue
  9. | release net_lvalue
  10. variable_assignment ::=
  11. variable_lvalue = expression
  12. net_lvalue ::= (From A.8.5)
  13. hierarchical_net_identifier [{[ constant_expression ] } [constant_range_expression ]]
  14. | { net_lvalue { , net_lvalue } }
  15. variable_lvalue ::=
  16. hierarchical_variable_identifier [ { [ expression ] } [ range_expression ] ]
  17. | { variable_lvalue { , variable_lvalue } }

语法9-3-过程连续赋值的语法

9.3.1 assign和deassign 过程语句

        assign过程连续赋值语句应覆盖变量的所有过程赋值。deassign赋值程序语句应结束对变量的程序连续赋值。变量值应保持不变,直到通过程序赋值或程序连续赋值为变量赋值。例如,assign和deassign过程语句允许在D型边缘触发触发器上对异步clear/preset进行建模,其中当clear或preset激活时,时钟被禁止。如果将关键字assign应用于已经有程序连续赋值的变量,则新的程序连续赋权应在进行新的程序持续赋值之前取消对变量的赋值。

例如:以下示例显示了在具有预设和清除输入的D型触发器的行为描述中使用assign和deassign赋值过程语句:

  1. module dff (q, d, clear, preset, clock); output q;
  2. input d, clear, preset, clock;
  3. reg q;
  4. always @(clear or preset)
  5. if (!clear)
  6. assign q = 0;
  7. else if (!preset)
  8. assign q = 1;
  9. else
  10. deassign q;
  11. always @(posedge clock)
  12. q = d;
  13. endmodule

如果clear或preset为低,则输出q将持续保持在适当的恒定值,时钟上升沿不会影响q。
当clear或preset都为高时,q被取消分配。

9.3.2 The force and release procedural statements

另外一种形式的过程连续赋值由force和release过程语句提供。这些语句和assign-deassign语句对相似,但是force可以应用于nets和变量。赋值符号左侧可以是一个变量、net向量的常量位选择、或者拼接。但是不能是内存字(向量引用)或者向量变量的位选或者部分选择。

        对一个变量使用force语句,应该覆盖过程赋值或者assign过程连续赋值对该变量的赋值操作,直到一个release语句在该变量上执行。当被释放时,如果该变量没有同时有激活的assign过程连续赋值,该变量不会立即改变它的值。这个变量应该维护它当前的值直到下一个过程赋值或者过程连续赋值对该变量赋值。释放当前激活的assign程序连续赋值的变量应立即重新建立该赋值。

        对于net的force程序赋值应该覆盖所有对net的驱动——门输出,模块输出和连续赋值,直到一个release程序语句在net上执行。当released时,net的值由它的驱动立即赋值。

  1. module test;
  2. reg a, b, c, d;
  3. wire e;
  4. and and1 (e, a, b, c);
  5. initial begin
  6. $monitor("%d d=%b,e=%b", $stime, d, e);
  7. assign d = a & b & c;
  8. a = 1;
  9. b = 0;
  10. c = 1;
  11. #10;
  12. force d = (a | b | c);
  13. force e = (a | b | c);
  14. #10;
  15. release d;
  16. release e;
  17. #10 $finish;
  18. end
  19. endmodule
  20. Results:
  21. 0 d=0,e=0
  22. 10 d=1,e=1
  23. 20 d=0,e=0

        在这个例子中,10s后,force语句将强制d、e两个变量为1,1,20s后,release语句释放d、e变量后,与门实例and1对e的赋值和assign语句对d的赋值再次激活。

        程序连续赋值或force语句的右侧可以是表达式。这应视为连续赋值;也就是说,如果赋值右侧的任何变量发生变化,则assign或force生效时应重新计算赋值。
例如:  force a = b + f(c) ;

这里,如果b改变或c改变,a将被强制为表达式b+f(c)的新值。

9.4 Conditional statement

        条件语句(或if-else语句)用于决定是否执行语句。形式上,语法在语法9-4中给出。

  1. conditional_statement ::= (From A.6.6)
  2. if ( expression )
  3. statement_or_null [ else statement_or_null ]
  4. | if_else_if_statement

语法9-4-if语句的语法

        如果表达式的计算结果为true(即已知值为非零),则应执行第一条语句。如果它的计算结果为false(即具有零值或值为x或z),则不执行第一条语句。如果存在else语句且表达式为false,则应执行else语句。

        因为if-else的else部分是可选的,所以当从嵌套的if序列中省略else时可能会产生混淆。如果缺少else,则始终将else与最近的前一个相关联,以解决此问题。在下面的示例中,else与内部if一起使用,如缩进所示。

  1. if (index > 0)
  2. if (rega > regb)
  3. result = rega;
  4. else // else applies to preceding if
  5. result = regb;

        如果不需要该关联,则应使用begin-end块语句来强制正确的关联,如下所示。

  1. if (index > 0)
  2. begin
  3. if (rega > regb)
  4. result = rega;
  5. end
  6. else result = regb;

9.4.1 If-else-if construct 

  1. if_else_if_statement ::= (From A.6.6)
  2. if ( expression ) statement_or_null
  3. { else if ( expression ) statement_or_null }
  4. [ else statement_or_null ]

语法9-5-if-else if构造的语法

        这种if语句序列(称为if-else if构造)是编写多路决策的最常用方法。表达式应按顺序计算。
如果任何表达式为真,则应执行与其关联的语句,这将终止整个链。每条语句要么是一条语句,要么是一个语句块。
        if-else if构造的最后一个else部分处理上述任何一个都不满足的情况或其他任何条件都不满足时的默认情况。有时默认情况下没有明确的操作。在这种情况下,可以省略尾随的else语句,也可以将其用于错误检查以捕获不可能的条件。

        下面的模块片段使用if-else语句测试变量index,以确定是否必须将三个modify_segn寄存器中的一个添加到内存地址,以及将哪个增量添加到索引reg。

  1. // declare regs and parameters
  2. reg [31:0] instruction, segment_area[255:0]; reg [7:0] index;
  3. reg [5:0] modify_seg1,
  4. modify_seg2,
  5. modify_seg3;
  6. parameter
  7. segment1 = 0, inc_seg1 = 1, segment2 = 20,
  8. inc_seg2 = 2, segment3 = 64, inc_seg3 = 4,
  9. data = 128;
  10. // test the index variable
  11. if (index < segment2) begin
  12. instruction = segment_area [index + modify_seg1];
  13. index = index + inc_seg1;
  14. end
  15. else if (index < segment3) begin
  16. instruction = segment_area [index + modify_seg2];
  17. index = index + inc_seg2;
  18. end
  19. else if (index < data) begin
  20. instruction = segment_area [index + modify_seg3];
  21. index = index + inc_seg3;
  22. end
  23. else
  24. instruction = segment_area [index];

9.5 Case statement

        case语句是一个多路决策语句,用于测试表达式是否与其他多个表达式中的一个匹配,并相应地进行分支。case语句的语法如语法9-6所示。

  1. case_statement ::= (From A.6.7)
  2. case ( expression )
  3. case_item { case_item } endcase
  4. | casez ( expression )
  5. case_item { case_item } endcase
  6. | casex ( expression )
  7. case_item { case_item } endcase
  8. case_item ::=
  9. expression { , expression } : statement_or_null
  10. | default [ : ] statement_or_null

语法9-6-case语句的语法

        默认语句应为可选语句。在一个case语句中使用多个默认语句是非法的。case表达式和case项表达式可以在运行时计算;这两个表达式都不需要是常量表达式。
        例如:使用case语句的一个简单示例是对寄存器 rega进行解码,以产生如下结果值:

  1. reg [15:0] rega;
  2. reg [9:0] result;
  3. case (rega)
  4. 16'd0: result = 10'b0111111111;
  5. 16'd1: result = 10'b1011111111;
  6. 16'd2: result = 10'b1101111111;
  7. 16'd3: result = 10'b1110111111;
  8. 16'd4: result = 10'b1111011111;
  9. 16'd5: result = 10'b1111101111;
  10. 16'd6: result = 10'b1111110111;
  11. 16'd7: result = 10'b1111111011;
  12. 16'd8: result = 10'b1111111101;
  13. 16'd9: result = 10'b1111111110;
  14. default result = 'bx;
  15. endcase

        括号中给出的case表达式应在任何case项表达式之前计算一次。case项目表达式应按照给出的确切顺序进行评估和比较。如果存在默认case项目,则在线性搜索期间将忽略该项目。在线性搜索期间,如果其中一个case项目表达式与括号中给出的case表达式匹配,则应执行与该case项目相关的语句,并且线性搜索应终止。如果所有比较都失败,并且给出了默认项,则应执行默认项语句。如果未给出默认语句,且所有比较都失败,则不应执行任何case项语句。

        除了语法之外,case语句与多路选择 if-else-if构造的不同之处在于两个重要方面:

a) if-else-if结构中的条件表达式比比较一个表达式与其他几个表达式更为通用,如case语句中的表达式。
b) 当表达式中有x和z值时,case语句提供了确定的结果。

        在case表达式比较中,只有当每个位与值0、1、x和z完全匹配时,比较才会成功。因此,在case语句中指定表达式时需要小心。所有表达式的位长度应相等,以便可以进行精确的位匹配。
所有case项表达式以及括号中的case表达式的长度应等于最长case表达式和case项表达式的长度。如果这些表达式中的任何一个是无符号的,则所有表达式都应被视为无符号表达式。如果所有这些表达式是有符号的,则应视为是有符号数。提供处理x和z值的case表达式比较的原因是,它提供了一种检测这些值的机制,并减少了由于这些值的存在而产生的悲观情绪。
例如:

示例1-以下示例说明了如何使用case语句正确处理x和z值:

  1. case (select[1:2])
  2. 2'b00: result = 0;
  3. 2'b01: result = flaga;
  4. 2'b0x,
  5. 2'b0z: result = flaga ? 'bx : 0;
  6. 2'b10: result = flagb;
  7. 2'bx0,
  8. 2'bz0: result = flagb ? 'bx : 0;
  9. default result = 'bx;
  10. endcase

        在本例中,如果select[1]为0,flaga为0,则即使select[2]的值为x或z,结果也应为0,这由第三种情况解决。

示例2-以下示例显示了使用case语句检测x和z值的另一种方法:

  1. case (sig)
  2. 1'bz: $display("signal is floating");
  3. 1'bx: $display("signal is unknown");
  4. default: $display("signal is %b", sig);
  5. endcase

9.5.1 Case statement with do-not-cares

        提供了两种其他类型的case语句,以允许在case比较中处理不关心的情况。其中一个将高阻态(z)视为不关心,另一个将高阻态和未知(x)值视为不在乎。这些case语句可以以与传统case语句相同的方式使用,但它们分别以关键字casez和casex开头。case表达式或case项的任何位中的不关心值(casez中的z值、casex中的z值和x值)应视为比较期间的不关心条件,且不应考虑该位位置。case表达式中的不关心条件可用于动态控制在任何时候应比较哪些位。文字数字的语法允许在这些case语句中使用问号(?)代替z。这为case语句中不关心位的规范提供了方便的格式。
例如:示例1-以下是casez语句的示例。它演示了指令解码,其中最高有效位的值选择应该调用哪个任务。如果ir的最高有效位是1,那么不管ir的其他位的值如何,都会调用任务指令1。

  1. reg [7:0] ir;
  2. casez (ir)
  3. 8'b1???????: instruction1(ir);
  4. 8'b01??????: instruction2(ir);
  5. 8'b00010???: instruction3(ir);
  6. 8'b000001??: instruction4(ir);
  7. endcase

示例2-下面是casex语句的示例。它演示了如何在模拟过程中动态控制不关心条件的极端情况。
在这种情况下,如果r=8'b01100110,则调用任务stat2。

  1. reg [7:0] r, mask;
  2. mask = 8'bx0x0x0x0;
  3. casex (r ^ mask)
  4. 8'b001100xx: stat1;
  5. 8'b1100xx00: stat2;
  6. 8'b00xx0011: stat3;
  7. 8'bxx010100: stat4;
  8. endcase

9.5.2 Constant expression in case statement

常量表达式可以用于case表达式。常量表达式的值应与case项表达式进行比较。
例如:以下示例通过对3位优先级编码器建模来演示用法:

  1. reg [2:0] encode ;
  2. case (1)
  3. encode[2] : $display("Select Line 2") ;
  4. encode[1] : $display("Select Line 1") ;
  5. encode[0] : $display("Select Line 0") ;
  6. default $display("Error: One of the bits expected ON");
  7. endcase

在本例中,case表达式是常量表达式(1)。case项目是表达式(位选择),并与常量表达式进行比较以进行匹配。

9.6 Looping statements

        语句有四种类型。这些语句提供了一种控制语句执行零次、一次或多次的方法。

forever   连续执行语句。

repeat    执行语句固定次数。如果表达式评估为未知或高阻抗,则应将其视为零,且不得执行任何语句。

while    执行语句,直到表达式变为false。如果表达式以false开头,则根本不应执行该语句。

for   通过三步流程控制其关联语句的执行,如下所示:

         a) 执行通常用于初始化变量的赋值,该变量控制执行的循环数。
        b) 计算表达式。如果结果为零,则应退出for循环。如果不为零,for循环将执行其关联语句,然后执行步骤c)。如果表达式评估为未知或高阻抗值,则应将其视为零。
         c) 执行通常用于修改循环控制变量值的赋值,然后重复步骤b)。

语法9-7显示了各种循环语句的语法。
 

  1. loop_statement ::= (From A.6.8)
  2.         forever statement 
  3.          | repeat ( expression ) statement 
  4.          | while ( expression ) statement 
  5.          | for ( variable_assignment ;  expression ; variable_assignment )
  6.            statement 

语法9-7-循环语句的语法

本小节的其余部分给出了三个循环语句的示例。forever循环只能与定时控制或disable语句一起使用;因此,该示例在9.7.2中给出。

例如:

例1:repeat语句,在以下重复循环示例中,加法和移位运算符实现乘数:

  1. parameter size = 8, longsize = 16;
  2. reg [size:1] opa, opb;
  3. reg [longsize:1] result;
  4. begin : mult
  5. reg [longsize:1] shift_opa, shift_opb; shift_opa = opa;
  6. shift_opb = opb;
  7. result = 0;
  8. repeat (size) begin
  9. if (shift_opb[1])
  10. result = result + shift_opa;
  11. shift_opa = shift_opa << 1;
  12. shift_opb = shift_opb >> 1;
  13. end
  14. end

示例2-While语句:以下示例统计rega中逻辑1值的数量:

  1. begin : count1s
  2. reg [7:0] tempreg;
  3. count = 0;
  4. tempreg = rega;
  5. while (tempreg) begin
  6. if (tempreg[0])
  7. count = count + 1;
  8. tempreg = tempreg >> 1;
  9. end
  10. end

示例3-for 语句:for语句实现了与以下基于while循环的伪代码相同的结果:

  1. begin
  2. initial_assignment;
  3. while (condition) begin
  4. statement
  5. step_assignment;
  6. end
  7. end

for循环仅使用两行来实现此逻辑,如下面的伪代码所示:

  1. for (initial_assignment; condition; step_assignment)
  2. statement

9.7程序定时控制

        Verilog HDL有两种类型的显式定时控制,可以控制过程语句何时发生。第一种类型是延迟控件,其中表达式指定最初遇到语句和语句实际执行之间的持续时间。延迟表达式可以是电路状态的动态函数,但它可以是一个简单的数字,用于在时间上分隔语句执行。当指定仿真波形描述时,延迟控制是一个重要特征。9.7.1和9.7.7中对此进行了描述。

        第二种类型的定时控制是事件表达式,它允许语句执行被延迟,直到在与该过程同时执行的过程中发生某个模拟事件。模拟事件可以是net或变量值的变化(隐式事件),也可以是由其他过程触发的显式命名事件(显式事件)的发生。最常见的情况是,事件控制是时钟信号的上升沿或下降沿。9.7.2至9.7.7中讨论了事件控制。到目前为止遇到的过程语句都在不提前模拟时间的情况下执行。模拟时间可以通过以下三种方法之一提前:

        —延迟控制,由符号引入#
        —事件控件,由符号引入@
        —该wait语句的操作类似于事件控件和while循环的组合

语法9-8描述了过程语句中的定时控制。

  1. delay_control ::= (From A.6.5)
  2. # delay_value
  3. | # ( mintypmax_expression )
  4. event_control ::=
  5. @ hierarchical_event_identifier
  6. | @ ( event_expression )
  7. | @*
  8. | @ (*)
  9. procedural_timing_control ::=
  10. delay_control
  11. | event_control
  12. procedural_timing_control_statement ::=
  13. | procedural_timing_control statement_or_null

如第6条所述,门延迟和网延迟也提前了模拟时间。9.7.1至9.7.7中讨论了三种程序定时控制方法。

9.7.1 Delay control、

        延迟控制之后的程序性语句应相对于延迟控制之前的程序性语句延迟执行规定的延迟。如果延迟表达式评估为未知或高阻抗值,则应将其解释为零延迟。如果延迟表达式的计算结果为负值,则应将其解释为与时间变量大小相同的二进制补码无符号整数。延迟表达式中允许指定参数。它们可以被SDF注释覆盖,在这种情况下,表达式将被重新计算。

例如:示例1-以下示例将分配的执行延迟10个时间单位:

#10 rega = regb;

9.7.2 Event control(事件控制)
        过程语句的执行可以与net或变量的值更改或声明事件的发生同步。net和变量上的值更改可以用作触发语句执行的事件。这被称为检测隐式事件。事件也可以基于变化的方向,即朝向值1(上升沿)或朝向值0(下降沿)。上升沿和下降沿事件的行为如表9-1所示,可描述如下:

        ——应在从1到x、z或0以及从x或z到0的过渡处检测到下降沿
        ——应在从0到x、z或1以及从x或z到1的过渡处检测到上升沿

         表达式值的任何变化都应检测到隐含事件。边沿事件只能在表达式的最低有效位检测到。表达式的任何操作数中的值发生变化而表达式的结果未发生变化时,不得将其检测为事件。

例如:以下示例显示了边沿控制语句的图示:

  1. @r rega = regb; // controlled by any value change in the reg r
  2. @(posedge clock) rega = regb; // controlled by posedge on clock
  3. forever @(negedge clock) rega = regb; // controlled by negative edge

9.7.3Named events(命名事件)
        除了net和变量之外,还可以声明一种新的数据类型,称为event。声明为事件数据类型的标识符称为命名事件。可以显式触发命名事件。它可以在事件表达式中使用,以与9.7.2中描述的事件控件相同的方式控制过程语句的执行。可以使命名事件从过程中发生。这允许控制在其他过程中启用多个操作。事件名称在使用之前应明确声明。语法9-9给出了声明事件的语法。

        事件名称在使用之前应明确声明。语法9-9给出了声明事件的语法。

  1. event_declaration ::= (From A.2.1.3)
  2. event list_of_event_identifiers ;
  3. list_of_event_identifiers ::= (From A.2.3)
  4. event_identifier { dimension }
  5. { , event_identifier { dimension } }
  6. dimension ::= (From A.2.5)
  7. [ dimension_constant_expression : dimension_constant_expression ]

语法9-9事件声明语法

事件不应保存任何数据。以下是命名事件的特征:

——它可以在任何特定的时间发生。
——它没有持续时间。
——使用9.7.2中描述的事件控制语法可以识别其发生。

        通过使用语法9-10中给出的语法激活事件触发语句,使声明的事件发生。通过更改事件控件表达式中事件数组的索引不会使事件发生。

  1. event_trigger ::= (From A.6.5)
  2. -> hierarchical_event_identifier { [ expression ] } ;

语法9-10-事件触发器的语法

        事件控制语句(例如,@trig rega=regb;)应使其包含过程的模拟等待,直到某个其他过程执行适当的事件触发语句(例如,->trig)。命名事件和事件控制为描述两个或多个并发活动进程之间的通信和同步提供了一种强大而有效的方法。

9.7.4Event or operator(事件或操作符)
        可以表示任意数量的事件的逻辑或,以便任何一个事件的发生都会触发其后的过程语句的执行。关键字或逗号字符(,)用作事件逻辑或运算符。可以在同一事件表达式中使用这些参数的组合。逗号分隔的敏感度列表应与or分隔的敏感度列表同义。
例如:
下面两个示例分别显示了两个和三个事件的逻辑或:

  1. @(trig or enable) rega = regb; // controlled by trig or enable
  2. @(posedge clk_a or posedge clk_b or trig) rega = regb;

9.7.5隐式事件表达式列表(Implicit event_expression list)

        事件控件的event_expression列表是寄存器传输级(RTL)模拟中常见的错误源。用户往往会忘记添加定时控制语句中读取的一些net或变量。当比较设计的RTL和门级版本时,通常会发现这一点。隐式事件表达式@*是一种方便的速记,它通过将过程实践控制语句(可以是语句组)读取的所有net和变量添加到事件表达式中来消除这些问题。

        语句中出现的所有net和变量标识符都将自动添加到事件表达式中,但以下情况除外:

——仅出现在等待或事件表达式中的标识符。
——仅在赋值左侧的变量中显示为层次化变量标识符的标识符。

        出现在赋值右侧、函数和任务调用中、case语句和条件表达式中、赋值左侧的索引变量或case项表达式中的变量都应包含在这些规则中。

  1. 例如:示例1
  2. always @(*) // equivalent to @(a or b or c or d or f)
  3. y = (a & b) | (c & d) | myfunction(f);
  1. Example 2
  2. always @* begin // equivalent to @(a or b or c or d or tmp1 or tmp2)
  3. tmp1 = a & b;
  4. tmp2 = c & d;
  5. y = tmp1 | tmp2;
  6. end
  1. Example 3
  2. always @* begin // equivalent to @(b)
  3. @(i) kid = b; // i is not added to @*
  4. end
  1. Example 4
  2. always @* begin // equivalent to @(a or b or c or d)
  3. x = a ^ b;
  4. @* // equivalent to @(c or d)
  5. x = c ^ d;
  6. end
  1. Example 5
  2. always @* begin // same as @(a or en)
  3. y = 8'hff;
  4. y[a] = !en;
  5. end
  1. Example 6
  2. always @* begin // same as @(state or go or ws)
  3. next = 4'b0;
  4. case (1'b1)
  5. state[IDLE]: if (go) next[READ] = 1'b1;
  6. else next[IDLE] = 1'b1;
  7. state[READ]: next[DLY ] = 1'b1;
  8. state[DLY ]: if (!ws) next[DONE] = 1'b1;
  9. else
  10. next[READ] = 1'b1;
  11. state[DONE]: next[IDLE] = 1'b1;
  12. endcase
  13. end

9.7.6Level-sensitive event control(电平敏感事件控制)
        过程语句的执行也可以延迟,直到条件变为真。这是使用wait语句完成的,wait语句是一种特殊形式的事件控制。wait语句的性质是电平敏感的,而不是基本的事件控制(由@字符指定),后者是边沿敏感的。等待语句应评估条件;如果为假,则等待语句之后的过程语句应保持阻塞状态,直到该条件变为真,然后继续。wait语句的形式如语法9-11所示。

  1. wait_statement ::= (From A.6.5)
  2.         wait ( expression ) statement_or_null 

语法9-11-wait语句的语法

例如:下面的示例显示了使用wait语句来完成电平敏感的事件控制:

  1. begin
  2. wait (!enable)
  3. #10 a = b;
  4. #10 c = d;
  5. end

        如果在进入块时enable的值为1,则wait语句将延迟下一条语句的求值(#10 a=b;),直到enable值变为0。如果在进入begin-end块时enable已经为0,则在延迟10之后评估赋值“a=b;”,并且不会发生额外的延迟。

9.7.7Intra-assignment timing controls (赋值内时间控制)
        前面描述的延迟和事件控制结构位于语句之前并延迟其执行。相反,赋值内延迟和事件控制包含在赋值语句中,并以不同的方式修改活动流。本子条款描述了赋值内定时控制和可用于赋值内延迟的重复定时控制的目的。赋值内延迟或事件控制应延迟将新值分配到左侧,但右侧表达式应在延迟之前而不是延迟之后进行评估。语法9-12给出了赋值内延迟和事件控制的语法。

  1. blocking_assignment ::= (From A.6.2)
  2. variable_lvalue = [ delay_or_event_control ] expression
  3. nonblocking_assignment ::=
  4. variable_lvalue <= [ delay_or_event_control ] expression
  5. delay_control ::= (From A.6.5)
  6. # delay_value
  7. | # ( mintypmax_expression )
  8. delay_or_event_control ::=
  9. delay_control
  10. | event_control
  11. | repeat ( expression ) event_control
  12. event_control ::=
  13. @ hierarchical_event_identifier
  14. | @ ( event_expression )
  15. | @*
  16. | @ (*)
  17. event_expression ::=
  18. expression
  19. | posedge expression
  20. | negedge expression
  21. | event_expression or event_expression
  22. | event_expression , event_expression

语法9-12赋值内延迟和事件控制的语法

        赋值内延迟和事件控制可以应用于阻塞分配和非阻塞分配。重复事件控制应规定指定事件发生次数的赋值内延迟。如果在求值时重复计数值或包含重复计数的带符号寄存器小于或等于0,则赋值发生时就好像没有重复构造一样。

  1. 例如:
  2. repeat (-3) @ (event_expression) // will not execute event_expression.
  3. repeat (a) @ (event_expression)
  4. // if a is assigned -3, it will execute the event_expression
  5. // if a is declared as an unsigned reg, but not if a is signed

当事件必须与时钟信号的计数同步时,这种构造很方便。
例如:表9-2通过显示可以在不使用内部分配的情况下实现相同定时效果的代码来说明内部分配定时控制的原理。

        接下来的三个示例使用fork-join行为构造。关键字fork和join之间的所有语句同时执行。
9.8.2中更详细地描述了这种结构。下面的示例显示了可以通过使用分配内定时控制来防止的竞争条件:

  1. fork
  2. #5 a = b;
  3. #5 b = a;
  4. join

本示例中的代码在同一模拟时间采样并设置a和b的值,从而创建竞争条件。下一个示例中使用的定时控制的内部分配形式防止了这种竞争情况。

  1. fork // data swap
  2. a = #5 b;
  3. b = #5 a;
  4. join

        分配内定时控制有效,因为分配内延迟导致在延迟之前评估a和b的值,并导致在延迟之后进行分配。一些实现内部分配定时控制的现有工具在计算右侧的每个表达式时使用临时存储。内部分配等待事件也是有效的。在下面的示例中,当遇到赋值语句时,会对右侧表达式进行求值,但赋值会延迟到时钟信号的上升沿:

  1. fork // data shift
  2. a = @(posedge clk) b;
  3. b = @(posedge clk) c;
  4. join

以下是作为非阻塞分配的分配内延迟的重复事件控制的示例:

a <= repeat(5) @(posedge clk) data;

图9-1说明了重复事件控制产生的活动。

 在这个例子中,当遇到这个赋值语句时,data的值会被计算。经过5次posedge clk,data的值将会被赋值给a。

以下是重复事件控件的示例,其中表达式包含指定事件发生次数和计数事件的操作:

                a <= repeat(a+b) @(posedge phi1 or negedge phi2) data;

在本例中,当遇到赋值语句时,将计算data的值。在phi1的上升沿和phi2的下降沿之和等于a和b之和之后,data的值被分配给a。尽管posedge phi1和negedge phi2在同一个仿真时间发生,每个将会被单独检测。

9.8 Block statements(块语句)

块语句是一种将语句分组在一起的方法,以便它们在语法上像单个语句一样。Verilog HDL中有两种类型的块:

——顺序块,也称为begin- end块
——并行块,也称为fork-join连接块

顺序块应由关键字begin和end分隔。顺序块中的程序语句应按给定顺序顺序执行。

并行块应由关键字fork和join分隔。并行块中的程序语句应同时执行。

9.8.1 Sequential blocks(顺序块)

顺序块应具有以下特征:

——语句应依次执行。
——每个语句的延迟值应相对于前一语句执行的模拟时间进行处理。
——控件应在最后一条语句执行后从块中传出。

语法9-13给出了顺序块的形式语法。

  1. seq_block ::= (From A.6.3)
  2. begin [ : block_identifier
  3. { block_item_declaration } ] { statement }
  4. end
  5. block_item_declaration ::= (From A.2.8)
  6. { attribute_instance } reg [ signed ] [ range ] list_of_block_variable_identifiers ;
  7. | { attribute_instance } integer list_of_block_variable_identifiers ;
  8. | { attribute_instance } time list_of_block_variable_identifiers ;
  9. | { attribute_instance } real list_of_block_real_identifiers ;
  10. | { attribute_instance } realtime list_of_block_real_identifiers ;
  11. | { attribute_instance } event_declaration
  12. | { attribute_instance } local_parameter_declaration ;
  13. | { attribute_instance } parameter_declaration ;

语法9-13顺序块语法

例如:示例1-顺序块使以下两个分配具有确定性结果:

  1. begin
  2. areg = breg;
  3. creg = areg; // creg stores the value of breg
  4. end

9.8.2 Parallel blocks(并行块)
平行块应具有以下特征:

——语句应同时执行。
——每个语句的延迟值应与进入块的模拟时间相关。
——延迟控制可用于为赋值提供时间排序。
——当执行最后一条按时间排序的语句时,控件应传递出块。

语法9-14给出了并行块的形式语法。

  1. par_block ::= (From A.6.3)
  2. fork [ : block_identifier
  3. { block_item_declaration } ] { statement }
  4. join
  5. block_item_declaration ::= (From A.2.8)
  6. { attribute_instance } reg [ signed ] [ range ] list_of_block_variable_identifiers ;
  7. | { attribute_instance } integer list_of_block_variable_identifiers ;
  8. | { attribute_instance } time list_of_block_variable_identifiers ;
  9. | { attribute_instance } real list_of_block_real_identifiers ;
  10. | { attribute_instance } realtime list_of_block_real_identifiers ;
  11. | { attribute_instance } event_declaration
  12. | { attribute_instance } local_parameter_declaration ;
  13. | { attribute_instance } parameter_declaration ;

语法9-14并行块语法

fork-join块中的计时控件不必按时间顺序排序。

   9.8.3 Block names(块名称)

可以通过在关键字begin或fork之后添加:name_of_block来命名顺序块和并行块。块的命名有几个目的:

——它允许为块声明局部变量、参数和命名事件。
——它允许在disable语句等语句中引用块(参见10.3)。

9.8.4 Start and finish times(开始和结束时间)
        顺序块和并行块都有开始和结束时间的概念。对于顺序块,开始时间是执行第一条语句时,结束时间是执行最后一条语句时。对于并行块,所有语句的开始时间都是相同的,而完成时间是执行最后一条按时间排序的语句的时间。顺序块和并行块可以相互嵌入,使得复杂的控制结构易于表达,并且具有高度的结构。当块相互嵌入时,块开始和结束的时间很重要。在达到块的完成时间之前,即在块完全完成执行之前,执行不得继续到块后面的语句。
例如:示例1-下面的示例显示了9.8.2中示例中的语句,这些语句以相反的顺序编写,但仍产生相同的波形。

示例2-当在两个单独的事件发生后进行分配时,称为事件连接,fork-join块可能很有用。例2中,fork-join块中的任何一个语句执行,将会跳出fork-join语句,执行下一条语句areg=breg。

  1. Example 2
  2. begin
  3. fork
  4. @Aevent;
  5. @Bevent;
  6. join
  7. areg=breg;
  8. end

这两个事件可以以任何顺序发生(甚至在同一模拟时间发生),fork-join块将完成,并进行赋值。
相反,如果fork-join块是一个begin-end块,并且Bevent发生在Aevent之前,那么该块将等待下一个Bevent。

示例3-此示例显示了两个连续的块,每个块将在其控制事件发生时执行。因为事件控件位于fork-join块内,所以它们并行执行,因此顺序块也可以并行执行。

  1. fork
  2. @enable_a
  3. begin
  4. #ta wa=0;
  5. #ta wa=1;
  6. #ta wa=0;
  7. end
  8. @enable_b
  9. begin
  10. #tb wb=1;
  11. #tb wb=0;
  12. #tb wb=1;
  13. end
  14. join

9.9Structured procedures (结构化程序)
Verilog HDL中的所有程序都在以下四个语句之一中指定:

——initial construct(初始化构造)

——always construct(always 构造)

——Task

——Function

initial构造和always 构造在模拟开始时启用。initial构造只能执行一次,语句完成后其活动应停止。
相反,always 构造应重复执行。仅当模拟终止时,其活动才应停止。initial构造和always构造之间不应有隐含的执行顺序。initial构造不需要在always构造之前调度和执行。模块中可以定义的initial和always构造的数量不应受到限制。任务和函数是从其他过程中的一个或多个位置启用的过程。第10条描述了任务和功能。
9.9.1initial构造
initial构造的语法在语法9-15中给出。

  1. initial_construct ::= (From A.6.2)
  2. initial statement

语法9-15initial构造的语法

例如:以下示例说明了在模拟开始时使用initial构造初始化变量。

  1. initial begin
  2. areg = 0; // initialize a reg
  3. for (index = 0; index < size; index = index + 1) memory[index] = 0;
  4. //initialize memory word
  5. end

9.9.2always struct 
always构造在整个模拟期间连续重复。语法9-16显示了always结构的语法。

  1. always_construct ::= (From A.6.2)
  2. always statement

语法9-16-always构造的语法

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/1001361
推荐阅读
相关标签
  

闽ICP备14008679号