草庐IT

3.4 Verilog 时序检查

runoob 2023-03-28 原文

关键词: setup hold recovery removal width period

指定路径延迟,目的是让仿真的时序更加接近实际数字电路的时序。利用时序约束对数字设计进行时序仿真,检查设计是否存在违反(violation)时序约束的地方,并加以修改,也是数字设计中不可或缺的过程。

Verilog 提供了一些系统任务,用于时序检查。这些系统任务只能在 specify 块中调用。下面就介绍 6 种常用的用于时序检查的系统任务:$setup, $hold, $recovery, $removal, $width 与 $period。

$setup, $hold

系统任务 $setup 用来检查设计中元件的建立时间约束条件,$hold 用来检查保持时间约束条件。其用法格式如下:

$setup(data_event, ref_event, setup_limit);
  • data_event: 被检查的信号,判断它是否违反约束
  • ref_event: 用于检查的参考信号,一般为时钟信号的跳变沿
  • setup_limit: 设置的最小建立时间

如果 T( ref_event - data_event) < setup_limit, 则会打印存在 violation 的报告。

$hold(ref_event, data_event, hold_limit);
  • data_event: 被检查的信号,判断它是否违反约束
  • ref_event: 用于检查的参考信号,一般为时钟信号的跳变沿
  • hold_limit: 设置的最小保持时间

如果 T( data_event - ref_event ) < hold_limit, 则会打印存在 violation 的报告。

注意: $setup 和 $hold 输入端口的位置是不同的。

Verilog 提供了同时检查建立时间和保持时间的系统任务:

$setuphold (ref_event, data_event, setup_limit, hold_limit);

下面完成一个数乘以 15 的操作,来说明 $setup 和 $hold 的用法。

Verilog 中,一个变量乘以常数一般用移位相加的方法来完成,例如对变量 num 乘以 15 的操作可以表示为:

num x 15 = (num << 3) + (num << 2) + (num << 1) + num

此操作需要 3 个加法器。下面对加法器进行建模,并指定路径延迟。

全加器功能描述可参考《Verilog 教程》的 3.1 节内容

实例


//单 bit 全加器,指定路径延迟
module full_adder1(
   input   Ai, Bi, Ci,
   output  So, Co);

   assign So = Ai ^ Bi ^ Ci ;
   assign Co = (Ai & Bi) | (Ci & (Ai | Bi));

   specify
      (Ai, Bi, Ci *> So) = 1.1 ;
      (Ai, Bi     *> Co) = 1.3 ;
      (Ci         => Co) = 1.2 ;
   endspecify
endmodule

//8bit 位宽加法器例化
module full_adder8(
     input [7:0]   a ,   //adder1
     input [7:0]   b ,   //adder2
     input         c ,   //input carry bit
     output [7:0]  so ,  //adding result
     output        co    //output carry bit
     );

   wire [7:0]      co_temp ;
   full_adder1  u_adder0(
                         .Ai     (a[0]),
                         .Bi     (b[0]),
                         .Ci     (c==1'b1 ? 1'b1 : 1'b0),
                         .So     (so[0]),
                         .Co     (co_temp[0]));

   genvar          i ;
   generate
      for(i=1; i<=7; i=i+1) begin: adder_gen
         full_adder1  u_adder(
                              .Ai     (a[i]),
                              .Bi     (b[i]),
                              .Ci     (co_temp[i-1]),
                              .So     (so[i]),
                              .Co     (co_temp[i]));
      end
   endgenerate
   assign co    = co_temp[7] ;
endmodule

8bit 位宽的触发器描述如下。触发器中指定路径延迟,并加入建立时间和保持时间的时序检查。

建立时间设置为 2ns,保持时间设置为 3ns。

实例


module D8(
    input       [7:0]   d ,
    input               clk ,
    output reg [7:0]    q);

   always @(posedge clk)
     q <= d ;
   specify
      $setup(d, posedge clk, 2);
      $hold(posedge clk, d, 3);
      (d,clk *> q) = 0.3 ;
   endspecify
endmodule

在 testbench 里完成乘以 15 的操作,并在一个周期内输出给下一级寄存器。

实例


`timescale 1ns/1ns
module test ;
   reg  [3:0]   a ;
   reg  [3:0]   b ;
   wire [3:0]   so ;
   wire         co ;

   parameter    CYCLE_10NS = 10ns;
   reg          clk ;
   initial begin
      clk = 0 ;
      # 111 ;
      forever begin
          #(CYCLE_10NS/2) clk = ~clk ;
      end
   end

   //需要乘以 15 的数
   reg [7:0]    num = 0 ;
   always @(posedge clk) begin
      num[3:0] <= num[3:0] + 1 ;
   end

   // num * 8 + num * 4
   wire [7:0]    adder1 ;
   full_adder8  u1_adder8(
               .a      (num<<2),
               .b      (num<<3),
               .c      (1'b0),
               .so     (adder1),
               .co     ());
   //num * 2 + num
   wire [7:0]    adder2 ;
   full_adder8  u2_adder8(
               .a      (num<<1),
               .b      (num),
               .c      (1'b0),
               .so     (adder2),
               .co     ());
   //num x 15
   wire [7:0]    adder3 ;
   full_adder8  u3_adder8(
               .a      (adder1),
               .b      (adder2),
               .c      (1'b0),
               .so     (adder3),
               .co     ());
   
   //store the result
   wire [7:0]    res_mul15 ;
   D8   data_store(
               .d       (adder3),
               .clk     (clk),
               .q       (res_mul15));

   initial begin
      forever begin
         #100;
         if ($time >= 1000)  $finish ;
      end
   end
endmodule // test

仿真报告中则出现了带有 setup/hold violation 的打印信息,部分截图如下。

截取出现 violation 时间的波形图,如下所示。

分析如下:

  • (1) 建立时间和保持时间均出现了 violation,虽然仿真中变量 num 乘以 15 以后延迟一个时钟周期的输出结果是正确的,但实际电路是很危险的。
  • (2) 波形中信号的建立时间 166-164.4 = 1.6 ns,小于设置的 2ns,所以会报告 violation。
  • (3) 波形中信号的保持时间 168.2-166 = 2.2 ns,小于设置的 3ns,所以会报告 violation。
  • (4) 图中红色部分是信号 d 变化的中间过程,因为信号各 bit 延迟不同,所以中间可能会出现多个不同的结果。

时序优化

保持时间的时序优化,在 RTL 层级描述上一般不好控制,这属于后端设计工程师的工作范畴,这里不做讨论。

本次主要简单探讨建立时间不满足约束条件时的优化问题。由上一节《3.3 建立时间和保持时间》中可知建立时间约束表达式为:

Tcq + Tcomb + Tsu <= Tclk + Tskew (1)
  • Tcq: 寄存器 clock 端到 Q 端的延迟;
  • Tcomb: data path 中的组合逻辑延迟;
  • Tsu: 建立时间;
  • Tclk: 时钟周期;
  • Tskew: 时钟偏移。

优化此不等式可从以下几个方面考虑:

  • (1) 选取时序较好的工艺原件,其 Tcq 和 Tsu 值越小越好;
  • (2) 优化组合逻辑,使组合逻辑延迟 Tcomb 越小越好;
  • (3) 降低工作时钟频率,增大工作时钟周期 Tclk;
  • (4) 增加时钟偏移 Tskew,但时钟偏移过大又会造成其他问题,例如保持时间可能不满足,功能逻辑错误等。

从 RTL 层次进行时序优化时,只能考虑方法(2)(3)。

例如,将上述仿真中的工作时钟周期由 10ns 改为 20ns,则不会出现 setup violation。

或者,调整逻辑,一个周期内完成 3 次加法运算,改为分散到两个周期内完成,中间增加一级寄存器进行缓冲,来减少时序上的压力。同时,变量 num 的变化周期也应该变为原来的 2 倍时长。

testbench 修改如下:

实例


`timescale 1ns/1ns
`define LOGIC_BUF
module test ;
   parameter    CYCLE_10NS = 10ns;
   reg          clk ;
   initial begin
      clk = 0 ;
      # 111 ;
      forever begin
          #(CYCLE_10NS/2) clk = ~clk ;
      end
   end

   reg          slow_flag = 0 ;
   always @(posedge clk) begin
`ifdef LOGIC_BUF
      slow_flag <= ~slow_flag ;
`else
      slow_flag <= 1'b1 ;
`endif
   end

   reg  [7:0]    num = 0 ;
   always @(posedge clk) begin
      if(slow_flag)
        num[3:0] <= num[3:0] + 1 ;
   end

   wire [7:0]    adder1 ;
   full_adder8  u1_adder8(
               .a      (num<<2),
               .b      (num<<3),
               .c      (1'b0),
               .so     (adder1),
               .co     ());

   wire [7:0]    adder2 ;
   full_adder8  u2_adder8(
               .a      (num<<1),
               .b      (num),
               .c      (1'b0),
               .so     (adder2),
               .co     ());

   //====== for better time=========
   //adding buffer
   wire [7:0]    adder1_r, adder2_r ;
   D8   adder1_buf(
               .d       (adder1),
               .clk     (clk),
               .q       (adder1_r));
   D8   adder2_buf(
               .d       (adder2),
               .clk     (clk),
               .q       (adder2_r));

`ifdef LOGIC_BUF
   wire [7:0]         adder1_t       = adder1_r ;
   wire [7:0]         adder2_t       = adder2_r ;
`else
   wire [7:0]         adder1_t       = adder1 ;
   wire [7:0]         adder2_t       = adder2 ;
`endif

   wire [7:0]    adder3 ;
   full_adder8  u3_adder8(
               .a      (adder1_t),
               .b      (adder2_t),
               .c      (1'b0),
               .so     (adder3),
               .co     ());

   wire [7:0]    res_mul15 ;
   D8   data_store(
               .d       (adder3),
               .clk     (clk),
               .q       (res_mul15));

   initial begin
      forever begin
         #100;
         if ($time >= 1000)  $finish ;
      end
   end

endmodule // test

此时仿真报告中不再有 violation,仿真截图如下。

由图可知,信号提前到达并保持不变的时间可达 8.6 ns,完全满足建立时间的时序要求。

此种方法的根本原理,是将信号多次变化的时序,分散在多个周期内,来满足时序约束的要求。此外,流水线设计,并行设计等都可以优化时序。

$recovery, $removal

建立时间和保持时间的概念都是出现在同步电路的设计中。

对于异步复位的触发器来说,异步复位信号也需要满足 recovery time(恢复时间)和 removal time(去除时间),才能有效的复位和释放复位,防止出现亚稳态。

释放复位时,复位信号在时钟有效沿来临之前就需要提前一段时间恢复到非复位状态,这段时间为 recovery time。类似于同步时钟下触发器的 setup time。

复位时,复位信号在时钟有效沿来临之后,还需要在一段时间内保持不变,这段时间为 removal time。类似于同步时钟下触发器的 hold time。

recovery 与 removal time 示意图如下所示。

系统任务 $recovery 与 $removal 分别用于 recovery 和 removal time 的检查,用法如下:

$recovery (ref_event, data_event, recovery_limit) ;
  • ref_event: 用于检查的参考信号,一般为清零或复位信号跳变沿;
  • data_event: 被检查的信号,一般为时钟信号跳变沿。
  • recovery_limit:设置的最小 recovery time。

当 ref_event (reset) < data_event (clock) 且 T(data_event - ref_event) < recovery_limit 时,即复位信号在时钟信号到来之前如果不满足 recovery time,则报告中会打印 violation。

$removal (ref_event, data_event, removal_limit) ;
  • ref_event: 用于检查的参考信号,一般为清零或复位信号跳变沿;
  • data_event: 被检查的信号,一般为时钟信号跳变沿。
  • removal_limit:设置的最小 removal time。

当 ref_event (reset) > data_event (clock) 且 T(ref_event - data_event) > removal_limit 时,即复位信号在时钟信号到来之后如果不满足 removal time,则报告中会打印 violation。

Verilog 提供了同时检查 revomal 和 recovery 的系统任务:

$recrem (ref_event, data_event, recovery_limit, removal_limit);

$width, $period

有些数字设计,例如 flash 存储器,还需要对脉冲宽度或周期进行检查,为此 Verilog 分别提供了系统任务 $width 和 $period。用法如下:

$width(ref_event, time_limit) ;
  • ref_event: 边沿触发事件
  • time_limit: 脉冲的最小宽度

$width 用于检查边沿触发事件 ref_event 到下一个反向跳变沿之间的时间,常用于脉冲宽度的检查。如果两次相反跳边沿之间的时间小于 time_limit,则会报告 violation。

$period(ref_event, time_limit) ;

$period 用于检查边沿触发事件 ref_event 到下一个同向跳变沿之间的时间,常用于时钟周期的检查。如果两次同向跳边沿之间的时间小于 time_limit,则报告中会打印 violation。

检查信号 CLK 宽度和周期的 specify 块描述如下:

实例


   specify
      $width(posedge CLK, 10);  
      $period(posedge CLK, 20);
   endspecify

本章节源码下载

Download

有关3.4 Verilog 时序检查的更多相关文章

  1. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  2. ruby - 检查数组是否在增加 - 2

    这个问题在这里已经有了答案:Checktoseeifanarrayisalreadysorted?(8个答案)关闭9年前。我只是想知道是否有办法检查数组是否在增加?这是我的解决方案,但我正在寻找更漂亮的方法:n=-1@arr.flatten.each{|e|returnfalseife

  3. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

  4. ruby - 检查字符串是否包含散列中的任何键并返回它包含的键的值 - 2

    我有一个包含多个键的散列和一个字符串,该字符串不包含散列中的任何键或包含一个键。h={"k1"=>"v1","k2"=>"v2","k3"=>"v3"}s="thisisanexamplestringthatmightoccurwithakeysomewhereinthestringk1(withspecialcharacterslike(^&*$#@!^&&*))"检查s是否包含h中的任何键的最佳方法是什么,如果包含,则返回它包含的键的值?例如,对于上面的h和s的例子,输出应该是v1。编辑:只有字符串是用户定义的。哈希将始终相同。 最佳答案

  5. ruby-on-rails - Ruby 检查日期时间是否为 iso8601 并保存 - 2

    我需要检查DateTime是否采用有效的ISO8601格式。喜欢:#iso8601?我检查了ruby​​是否有特定方法,但没有找到。目前我正在使用date.iso8601==date来检查这个。有什么好的方法吗?编辑解释我的环境,并改变问题的范围。因此,我的项目将使用jsapiFullCalendar,这就是我需要iso8601字符串格式的原因。我想知道更好或正确的方法是什么,以正确的格式将日期保存在数据库中,或者让ActiveRecord完成它们的工作并在我需要时间信息时对其进行操作。 最佳答案 我不太明白你的问题。我假设您想检查

  6. ruby - 检查日期是否在过去 7 天内 - 2

    我的日期格式如下:"%d-%m-%Y"(例如,今天的日期为07-09-2015),我想看看是不是在过去的七天内。谁能推荐一种方法? 最佳答案 你可以这样做:require"date"Date.today-7 关于ruby-检查日期是否在过去7天内,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/32438063/

  7. ruby - 检查是否通过 require 执行或导入了 Ruby 程序 - 2

    如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby​​文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否

  8. css - 用 watir 检查标签类? - 2

    我有一个div,它根据表单是否正确提交而改变。我想知道是否可以检查类的特定元素?开始元素看起来像这样。如果输入不正确,添加错误类。 最佳答案 试试这个:browser.div(:id=>"myerrortest").class_name更多信息:http://watir.github.com/watir-webdriver/doc/Watir/HTMLElement.html#class_name-instance_method另一种选择是只查看具有您期望的类的div是否存在browser.div((:id=>"myerrortes

  9. ruby-on-rails - rspec - 如何检查方法是否存在? - 2

    我的模型有defself.empty_building//stuffend我怎样才能对这个现有的进行rspec?,已经尝试过:describe"empty_building"dosubject{Building.new}it{shouldrespond_to:empty_building}endbutgetting:Failure/Error:it{shouldrespond_to:empty_building}expected#torespondto:empty_building 最佳答案 你有一个类方法self.empty_bu

  10. 屏幕录制为什么没声音?检查这2项,轻松解决 - 2

    相信很多人在录制视频的时候都会遇到各种各样的问题,比如录制的视频没有声音。屏幕录制为什么没声音?今天小编就和大家分享一下如何录制音画同步视频的具体操作方法。如果你有录制的视频没有声音,你可以试试这个方法。 一、检查是否打开电脑系统声音相信很多小伙伴在录制视频后会发现录制的视频没有声音,屏幕录制为什么没声音?如果当时没有打开音频录制,则录制好的视频是没有声音的。因此,建议在录制前进行检查。屏幕上没有声音,很可能是因为你的电脑系统的声音被禁止了。您只需打开电脑系统的声音,即可录制音频和图画同步视频。操作方法:步骤1:点击电脑屏幕右下侧的“小喇叭”图案,在上方的选项中,选择“声音”。 步骤2:在“声

随机推荐