SystemVerilog-Clocking

SystemVerilog-Clocking

在SystemVerilog中引入时钟块是为了解决在写testbench时对于特定时序和同步处理的要求而设计的。

时钟块是在一个特定的时钟上的一系列同步的信号,它基本上能够将testbench中与时序相关的结构、函数和过程块分开,能够帮助设计人员根据transaction 和 cycle完善testbench,时钟块只能在module、interface或program中声明。

First Example

这里有一个简单的示例,描述了SystemVerilog中的Clocking如何工作,这是一个可置位的二进制计数器

module COUNTER (input Clock, Reset, Enable, Load, UpDn,
                input [7:0] Data, output reg[7:0] Q);
  always @(posedge Clock or posedge Reset)
    if (Reset)
      Q <= 0;
    else
      if (Enable)
        if (Load)
          Q <= Data;
        else
          if (UpDn)
            Q <= Q + 1;
          else
            Q <= Q - 1;
endmodule

未采用时钟块计数器的testbench如下:

module Test_Counter;
  timeunit 1ns;

  reg Clock = 0, Reset, Enable, Load, UpDn;

  reg [7:0] Data;
  wire [7:0] Q;
  reg OK;

  // Clock generator
  always
  begin
    #5 Clock = 1;
    #5 Clock = 0;
  end

  // Test stimulus
  initial
  begin
    Enable = 0;
    Load = 0;
    UpDn = 1;
    Reset = 1;
    #10; // Should be reset
    Reset = 0;
    #10; // Should do nothing - not enabled
    Enable = 1;    #20; // Should count up to 2
    UpDn = 0;
    #40; // Should count downto 254
    UpDn = 1;

    // etc. ...
  end

  // Instance the device-under-test
  COUNTER G1 (Clock, Reset, Enable, Load, UpDn, Data, Q);

  // Check the results
  initial
  begin
    OK = 1;
    #9;
    if (Q !== 8'b00000000)
      OK = 0;
    #10;
    if (Q !== 8'b00000000)
      OK = 0;
    #20;
    if (Q !== 8'b00000010)
      OK = 0;
    #40;
    if (Q !== 8'b11111110)
      OK = 0;
    // etc. ...
  end
endmodule

采用了时钟块设计的testbench如下:

module Test_Counter_w_clocking;
  timeunit 1ns;

  reg Clock = 0, Reset, Enable, Load, UpDn;
  reg [7:0] Data;
  wire [7:0] Q;

  // Clock generator
  always
  begin
    #5 Clock = 1;
    #5 Clock = 0;
  end

  // Test program
  program test_counter;
    // SystemVerilog "clocking block"
    // Clocking outputs are DUT inputs and vice versa
    default clocking cb_counter @(posedge Clock);
      default input #1step output #4;
      output negedge Reset;
      output Enable, Load, UpDn, Data;
      input Q;
    endclocking

    // Apply the test stimulus
    initial begin

      // Set all inputs at the beginning    
      Enable = 0;            
      Load = 0;
      UpDn = 1;
      Reset = 1;

      // Will be applied on negedge of clock!
      ##1 cb_counter.Reset  <= 0;
      // Will be applied 4ns after the clock!
      ##1 cb_counter.Enable <= 1;
      ##2 cb_counter.UpDn   <= 0;
      ##4 cb_counter.UpDn   <= 1;
      // etc. ...      
    end

    // Check the results - could combine with stimulus block
    initial begin
      ##1   
      // Sampled 1ps (or whatever the precision is) before posedge clock
      ##1 assert (cb_counter.Q == 8'b00000000);
      ##1 assert (cb_counter.Q == 8'b00000000);
      ##2 assert (cb_counter.Q == 8'b00000010);
      ##4 assert (cb_counter.Q == 8'b11111110);
      // etc. ...      
     end

    // Simulation stops automatically when both initials have been completed

  endprogram

  // Instance the counter
  COUNTER G1 (Clock, Reset, Enable, Load, UpDn, Data, Q);

  // Instance the test program - not required, because program will be
  // instanced implicitly.
  // test_COUNTER T1 ();
endmodule

值得注意的是,在这个testbench模块中,时钟块嵌套在程序中(这样设计testbench的好处能够在Program article这篇文章中完全找到),程序块在不采用module和interface时也能嵌套,因此在多人合作开发的程序中可以共享局部设计( This way multiple co-operating programs can share variables local to the scope? 需要重新理解),无端口的嵌套程序或者顶层设计不需要(显示)实例化(而是隐式实例化,它的模块名同设计中的模块名一样)。

时钟块即是声明同时也是对声明的实例化,需要注意的一点是,在testbench中的时钟块的信号方向与testbench中的方向是相对的,因此Q是计数器的输出,是时钟块的输入,还有一点是时钟块中不需要定义宽度,只标明方向。

cb_counter时钟块中的信号与Clock的上升沿同步,默认情况下输入有 #1step的偏斜(skew),输出有4ns的时钟偏斜,时钟偏斜决定了从时钟触发开始到某个信号被采样或驱动的时间长度,输入信号偏斜为负(通常指时钟采样前的一段时间),输出偏斜通常指比采样时钟后的那一段时间。

输入时钟偏斜为 #1step 表示时钟的有效沿读取的值始终是紧接相应时钟沿之前的信号的最后一个值,step表示的是时间精度。

##运算符在testbench中用于延迟特定时钟沿或时钟周期单位。

Clocking Block Drives

时钟块的输出口和双向口能够在特定的时钟事件触发时和能够以特定的时钟偏斜驱动到其相应的信号上,要注意的一点是,驱动器不会改变时钟块双向口的输入。这是因为读取输入始终会产生最后的采样值,而不是驱动值。

同步信号驱动被视为非阻塞赋值,如果在同一仿真时间将多个同步驱动信号连接到同一时钟块的输入或输出上,就会运行错误,4态口会设为X,双态口会设为0。

如下是一些用时钟块的驱动信号的实例:

cb.Data[2:0] <= 3'h2;    // Drive 3-bit slice of Data in current cycle
##1 cb.Data <= 8'hz;         // Wait 1 Clk cycle and then drive Data
##2 cb.Data[1] <= 1;         // Wait 2 cycles, then drive bit 1 of Data
cb.Data <= ##1 Int_Data; // Remember the value of Int_Data, and then drive Data 1 Clk cycle later
cb.Data[7:4] <= 4'b0101;
cb.Data[7:4] <= 4'b0011; // Error: driven value of Data[7:4] is 4’b0xx1

Clocking Blocks and Interfaces

以下是一个用interface实现的多个时钟块,一个时钟块能够用一个interface去减少testbench中用于表示连接的代码。

在下面的testbench中可以看到(如 modport TestR),interface 的信号的方向与时钟块中指定的方向相同,而从DUT(如 modort Ram)看,接口信号方向相反,testbench中时钟块的信号方向是现对于testbench的,当modport声明能够用于描述任意方向(如: testbench or DUT),为了说明这一点,我们将实现两条总线,他们具有不同时钟,并且testbench与顶层分开,testbench作为program实现。

// Interface definitions
interface DataBus (input Clock);
  logic [7:0] Addr, Data;
  modport TestR (inout Addr, inout Data);
  modport Ram (inout Addr, inout Data);
endinterface

interface CtrlBus (input Clock);
  logic RWn;
  // RWn is output, as it is in the clocking block
  modport TestR (output RWn);
  // RWn is input, reversed than in the clocking block
  modport Ram (input RWn);
endinterface

// Testbench defined as a program, with two clocking blocks
program TestRAM (DataBus.TestR DataInt,
                 CtrlBus.TestR CtrlInt);
  clocking cb1 @(posedge DataInt.Clock);
    inout #5ns DataInt.Data;
    inout #2ns DataInt.Addr;
  endclocking

  clocking cb2 @(posedge CtrlInt.Clock);   
    output #10;
    output RWn = CtrlInt.RWn;  //  Hierarchical expression
  endclocking

  initial begin
    cb2.RWn = 0;
    cb1.DataInt.Data = 1;
    ...
  end
endprogram

module RAM (DataBus.Ram DataInt, CtrlBus.Ram CtrlInt);
  logic [7:0] mem[0:255];

  always @*
    if (CtrlInt.RWn)
      DataInt.Data = mem[DataInt.Addr];
    else
      mem[DataInt.Addr] = DataInt.Data;
endmodule

module Top;
  logic Clk1, Clk2;

  // Instance the interfaces
  DataBus TheDataBus(.Clock(Clk1));
  CtrlBus TheCtrlBus(.Clock(Clk2));

  RAM TheRAM (.DataBus.Ram(TheDataBus.Ram),
              .CtrlBus.Ram(TheCtrlBus.Ram)); // Connect them
  TestRAM TheTest (.DataBus.TestR(TheDataBus.TestR),
                   .CtrlBus.TestR(TheCtrlBus.TestR));
endmodule

Clocking block events

可以通过时钟块名称直接访问时钟块的时钟事件,如 @(cb) 等于@(posedge clk).可以通过用 时钟块名字和 (.) 操作符俩访问时钟块的各个信号,所有的event都会同步到时钟块。

以下是同步语句的一些示例:

// Wait for the next change of Data signal from the cb clocking block
@(cb.Data);

// Wait for positive edge of signal cb.Ack
@(posedge cb.Ack);

// Wait for posedge of signal cb.Ack or negedge of cb.Req
@(posedge cb.Ack or negedge cb.Req);

// Wait for the next change of bit 2 of cb.Data
@(cb.Data[2]);
// Wait for the next change of the specified slice
@(cb.Data[7:5]);

更多关于时钟块结构能够在 Accellera SystemVerilog LRM, section 15找到。

你可能感兴趣的:(SystemVerilog)