Verilog的时序问题和SystemVerilog TestBench激励时序

转载请标明出处:
原文发布于:[浅尝辄止,未尝不可的博客](https://blog.csdn.net/qq_31019565)

Verilog时序问题和SystemVerilog TestBench激励时序

最近我温习《SystemVerilog验证-测试平台编写指南(第二版)》这本书,看到了第4.3节,激励时序的问题,还是花了一些时间去看。SV这条路任重道远。本文中大部分是摘录,自己认为是重要的就留下了,希望可以加深理解,留下一些积累。有志同道合的朋友可以一起探讨,欢迎指教。

总述

TestBench和DUT之间的合理配合是非常重要的。在时钟周期合适的时间点,驱动和接收同步信号显得尤为重要。驱动太晚或者采样太早,测试平台的动作就可能错过一个时钟周期。即使在同一个Time Slot中,比如同一个时刻100ns,TestBench和DUT也会引起竞争。一个信号的同时被写入和读出,读取到的值到底是旧的还是新的,这是一个很大的问题。在Verilog中,非阻塞赋值可以在TestBench驱动DUT的时候解决掉这个问题。但是TestBench不能够确保采样到的DUT的值是最新值。SystemVerilog中的几种结构或许可以对控制时序有所帮助。

使用时钟块控制同步信号的时序

时钟块主要是为了用来指定同步信号相对于时钟的时序。在同一个时钟块中,所有的信号都会被同步的驱动和采样。这样就保证了测试平台在正确的时间点与信号进行交互。时钟块主要是在TestBench中使用。

//example_1
//interface with clocking block
interface example_1_interface(input bit clk);
  logic enable;
  logic [1:0] request,grant;
//clocking cb_1
  clocking cb_1 @(posedge clk);
    default input #1step output #0;
    input  enable;
    input  grant;
    output request;
  endclocking:cb_1
//clocking cb_2
  clocking cb_2 @(posedge clk);
    default input #1step output #0;
    input enable;
    input grant;
    input request;
  endclocking:cb_2
//modport 
  modport mp_1(clocking cb_1);
  modport mp_2(clocking cb_2);
endinterface:example_1_interface

如上代码所示,在一个interface中,可以包含多个clocking block。并且每一个块中都有一个时钟表达式,对应着一个时钟域。典型的时钟表达式 @(posedge clk),它定义了单时钟沿。示例代码中定义 default 语句,指定一个时钟偏移。但是在默认的情况下输入信号仅在设计执行前被采样,并且设计的输出信号在当前Time Slot又被驱动回当前设计,后文详述。

//example_2
//simple testbench : module test
module test(example_1_interface.mp_1 intf);
  initial begin
    intf.cb_1.enable <= 1'b1;
    @intf.cb_1;
    $display("%0t:Grant = %b",$time,intf.cb_1.grant);
  end
endmodule

如上代码所示,一旦定义了clocking block,TestBench就可以使用类似于 @intf.cb_1 表达式进行时钟等待,而不需要确切的描述时钟的边沿。example_1中定义了modport。每个modport中的信号的方向都是相对于modport而言的,这些信号也只在对应的modport中使用。

接口中的 logic 和 wire 对比

这本书出于易用性角度,建议将 interface 中的信号定义为 logic 类型。这里只说比较结果,对于需要采用哪种不做争论。

//example_3
//introducing how to drive logic type signals and wire type signals
//interface example_3_interface
interface example_3_interface();
  wire wire_type;
  logic logic_type;
endinterface:example_3_interface

//simple testbench : module test
module test (example_3_interface intf);
  logic local_wire;
  assign = intf.wire_type = local_wire;
  initial begin
    intf.logic_type <= 0;//直接驱动异步logic信号
    local_wire <= 1;//只能通过assign驱动wire信号
    ...
  end
endmodule 
  1. 在接口中如果使用过程赋值语句驱动一个异步信号,那么该信号必须是 logic 类型
  2. wire类型变量只能被连续赋值语句驱动
  3. 时钟块儿中的信号时钟是同步的,可以定义为logic或者wire
  4. 如上代码所示,logic 信号可以直接驱动,而 wire 需要额外的代码
  5. 接口中的信号使用logic的另一个原因是,如果使用了多个元件的驱动源,编译器会自动报错
  6. 如果当前项目代码重用于将来,变化的是一个logic信号被多个元件驱动,而该信号没有在任何一个时钟块,那么将不得不修改logic类型

Verilog的时序问题

DUT和TestBench 无论在逻辑上还是时序上都应该是相互独立的。在实际的硬件设计中,DUT中的存储单元在时钟的有效沿锁存输入信号。这些数值由存储单元输出,然后通过逻辑块到达下一个存储单元。从上一个存储单元的输入到下一个存储单元的输入的延时必须小于一个时钟周期。所以测试仪需要在时钟沿之后驱动芯片的输入,然后再下一个时钟沿之前读取输出。DUT应该在时钟有效沿或者有效沿之后被及驱动。然后在下一个有效沿到达之前,TestBench尽可能晚的进行采样。

问题被提出
如果DUT和测试程序只有Verilog构成,几乎是不可能实现的:

  1. 如果TestBench在时钟边沿驱动DUT,会造成竞争现象。
  2. 如果时钟到达一些DUT的时间快于激励,但是到达另一些DUT的时间又晚于激励,这样会导致:在DUT外部,时钟在相同的时间到达;在DUT内部,有些输入在上一个时钟周期采样,但是其他输入在当前的时钟周期采样。

解决方案
方案一,给系统加小小的延迟,#0。
方案二,给系统加较大的延迟,#1。
由于弊端的存在,会避免#0,#1解决时序问题

DUT和TestBench间的竞争状态

//example_4
//DUT:module memory
module memory(input wire start,write,
              input wire [7:0] addr,
              inout wire [7:0] data);
  logic [7:0] mem[256];
  always @(posedge start)begin
    if (write)
      mem[addr] <= data;
    ...
  end
endmodule

//testbench : module test
module test(output logic start,write,
            output logic [7:0] addr,data);
  initial begin
    start = 0;//信号初始化
    write = 0;
    #10//短暂的延时
    addr = 8'h42; //发起第一个命令
    data = 8'h5a;
    start = 1;
    write = 1;
    ...
  end
endmodule

如上代码所示,是一个DUT和TestBench可能存在竞争状态的实例。竞争状态出现在TestBench先产生Start信号的时候,当memory被start信号唤醒之后,write、addr、data,还保留着原来的值。TestBench和DUT都在用这些值,可能会造成竞争。同样对于DUT的输出,需要在时钟沿到达之前进行采样。

程序块(Program Block)和时序区域(Timing Region)

SystemVerilog的合理运用可以把TestBench和DUT的事件分开调度。SV中,TestBench中的代码在一个程序块儿中,跟模块非常类似。但区别是程序块不能够有任何的层次级别。
SystemVerilog引入了一种新的时间片的划分方式。

Created with Raphaël 2.2.0 Preponed Pre-Active Active Inactive Pre - NBA NBA Post - NBA Pre-Observed O b s e r v e d Post-Observed Reactive Re-inactive Pre-Re-NBA Re - NBA Post - Re - NBA P r e - Postponed Postponed yes no yes no yes no yes no yes no yes no yes no yes no yes no yes no yes no yes no yes no

将Time Slot划分为有序的区域是为了实现DUT和TestBench之间互相作用的可预测性。

  1. Active region sets 和 Reactive region sets
    Active region sets 和 Reactive region sets是两个非常重要的集合,被用来规划 SV 的有效动作。
    Active region sets包含:Active, Inactive, Pre-NBA, NBA, Post-NBA
    Reactive region sets包含:Re-Active, Re-Inactive, Re-Pre-NBA, Re-NBA, Re-Post-NBA
  2. Iterative regions
    Time slot的迭代区域:Active region sets + Pre-Observed、Observed、Post-Observed+Reactive region sets + Pre-Postponed
  3. Simulation region
    simulation 区域包含:Preponed, Active, Inactive, NBA, Observed, Reactive, Re-Inactive, Re-NBA and Postponed regions
    1)Preponed events region
    #1step 采样数据表示在当前时间片(TimeSlot)的preponed region进行采样。而在preponed region进行采样就相当于在上一个时间片的postponed region进行数据采样。
    2)Active events region
    活动事件区域控制当前Active region set中的要执行的时间,可以任意顺序执行。
    3)Inactive events region
    在处理完所有活动事件之后,非活动事件区域开始控制需要被执行的事件。
    如果事件在Active region set中被执行,那么一个显示的 #0 延迟控制将会将进程挂起并且将时间放在当前时间片的非活动区域,以便在下一个非活动到活动迭代中恢复进程。
    4)NBA(nonblocking assignment update) events region
    这个区域在Inactive events region完成后执行。
    如果时间在Active region set中被执行,则nonblocking assignment会在NBA区域中创建一个事件,这个事件安排当前和后续的仿真时间。
    5)Observed event region
    执行systemVerilog 断言
    6)Reactive events region
    checker或program block中的程序块分配和并非断言中的action block 指定的code,被安排在这个区域中。其他参照active events region。
    7)Reinactive events region
    参照inactive events region。
    8)Re-NBA events region
    参照NBA events region
    9)Postponed events region
    $monitor, $strobe或者其他相似的事件在本区域。
    这个区域不允许值进行改变。不允许写值,也不允许调度之前region的events。
  4. PLI region
    PLI 区域包含:Preponed, Pre-Active, Pre-NBA, Post-NBA, Pre-Observed, Post-Observed, Pre-Re-NBA, Post-Re-NBA and Pre-Postponed regions
    1)Preponed PLI region
    Preponed PLI region提供了一个PLI回调控制点,允许PLI应用程序在任何网络或变量改变状态之前访问当前时隙中的数据。在此区域内,向任何网络或变量写入值或在当前时间段内安排其他区域中的事件是非法的。
    2)Pre-Active PLI region
    Pre-Active PLI region提供了一个PLI回调控制点,允许PLI应用程序在控制活动区域中的事件之前读取和写入值并创建事件。
    3)Pre-NBA PLI region
    Pre-NBA PLI region提供了一个PLI回调控制点,允许PLI应用程序在控制NBA区域内的事件之前读取和写入值并创建事件
    4)Post-NBA PLI region
    Post-NBA PLI region提供了一个PLI回调控制点,允许PLI应用程序在控制NBA区域内的事件之后读取和写入值并创建事件
    5)Pre-Observed PLI region
    Pre-Observed PLI region提供了一个PLI回调控制点,允许PLI应用程序在活动区域集稳定后读取值。在此区域内,向任何网络或变量写入值或在当前时间段内安排事件是非法的。
    6)Post-Observed PLI region
    Post-Observed PLI region提供了一个PLI回调控制点,断言后读值。
    7)Pre-Re-NBA PLI region
    参照Pre-NBA PLI region
    8)Post-Re-NBA PLI region
    参照Post-NBA PLI region
    9)Pre-Postponed PLI region
    提供了一个PLI回调控制点,允许PLI应用程序在处理除Postponed region之外的所有其他区域后读取和写入值并创建事件。
    10)Postponed PLI region
    处理只读事件。

SystemVerilog主要调度区域:

区域名 行为
Active 仿真模块中的设计代码(RTL、时钟发生器、门级代码)
Observed 执行SystemVerilog 断言
Reactive 执行程序中的测试平台部分
Postponed 为测试平台的输入采样信号

时钟块的默认时序是在#1step延时之后采样输入信号,在#0延时之后驱动输出信号,1step延时规定了信号在前一个时间片的postpone区域,在设计有任何新的动作之前被采样,这样就可以在时钟改变之前获得输出值,因为时钟模块的原因,测试平台的输出信号是同步的,所以它们直接送入设计中。在Reactive区域运行的程序块在同一个时刻再一次触发Active区域。

仿真的结束

在Verilog中,仿真在调度事件存在的时候回继续执行,直到遇到$finish。SystemVerilog增加了一种结束仿真的方法。SV中任何一个程序块都被视为含有一个测试,如果仅仅有一个程序块,那么当完成所有initial块中的最后一个语句的时候,仿真就结束了。因为编译器认为这就是测试的结尾,即使还有模块或者程序块的线程在运行仿真也会结束。所以当测试结束时,无需关闭所有的monitor和driver。如果存在多个程序块,那么在最后一个程序块结束时,仿真结束。也可以用$exit提前中断一个程序块,或者用$finish结束仿真。

参考文献:
1、《SystemVerilog验证–测试平台编写指南(原书第二版)》
2、IEEE Standard for SystemVerilog—Unified Hardware Design,Specification, and Verification Language(version 2017)

你可能感兴趣的:(学习笔记,SystemVerilog,Verilog,TimingRegion,激励时序,Verification)