【SVA】功能覆盖率——断言常用写法

本文介绍了UVM中的断言分类,包括即时断言和并发断言,并详细阐述了如何在Verilog环境下使用generate、property、sequence、ifelse以及处理变量延迟进行功能覆盖率检查。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



前言

断言(assertion)是验证工作中常用的自动检查手段,相比于checker或者reference model更加轻量化也更方便移植,同时也是收集功能覆盖率的重要一环。


一、断言分类

1、即时断言 Immediate Assertions

即时断言作用和 if 语句的效果类似,在仿真运行到这句代码时做出判断。

int a;
assert(a) else `uvm_error(get_type_name(), "assert fail");
if(a) else `uvm_error(get_type_name(), "assert fail");

如上两句代码,在验证环境中效果是一样的,都是运行到这行代码时,a非0就报错。
需要注意的是,断言如果违例,默认报错的信息是“ Offending xxx ”,加上 else `uvm_error() 语句会更加方便。

2、并发断言 Concurrent Assertions

并发断言是最常用的,一般和时序强相关的检查都可以用这种形式实现,本文内容也主要描述并发断言。
并发断言需要指定采样时钟,可以在module、interface里实现,在整个仿真周期内都起效。

ast_ack_when_req: assert property(@(posedge clk) disable iff(~rstn) ack |-> req)
else `uvm_error("xxx", "ast_ack_when_req fail");

如上就是一个简单的检查,在rstn为1时生效,每当clk的上升沿判断,如果当拍a为高,检查下一拍b为高,否则报错。
符号|->称为蕴含符,只有当蕴含符前面的条件满足时,才检查后面的时序是否正确。

二、断言的层次结构

SVA中可以存在内建的单元,这些单元可以是以下的几种:

1、Boolean expressions 布尔表达式

布尔表达式是组成断言的最小单元,断言可以由多个逻辑事件组成,这些逻辑事件可以是简单的布尔表达式.在SVA中,信号或事件可以使用常用的操作符,如:&&, ||, !, ^,&等;

2、Sequence序列

sequence是布尔表达式更高一层的单元,而且可以表达时序关系。一个sequence中可以包含若干个布尔表达式,同时在sequence中可以使用一些新的操作符,如 ## 、重复操作符、序列操作符等。
sequence具有如下特性:
可带参数;
可以在 property 中调用;
可以使用局部变量;
可以定义时钟周期;
可以使用序列方法,如 ended、matched、triggered,这些序列方法只能在sequence中使用;

sequence seq(a, b);			//形参a,b方便复用
  @(posedge clk) a ##2 b; 	//在时钟clk上升沿采样,a为1时,两拍后b也为1
endsequence

seq方法:ended
默认情况下,多重sequence的组合是以sequence的起始时间作为同步标志的,就是以序列的起始点作为同步点,来组合成时间上连续的检查。
SVA提供ended结构以sequence的结束时间作为序列同步点。关键字ended存储一 个反映在指定时钟处序列是否匹配成功的布尔值。
ended代表匹配的完成,是匹配的结束点,而不是匹配的起点。

3、Property 属性

property是比sequence更高一层的单元,也是构成断言最常用的模块,其中最重要的性质是可以在property中使用蕴含操作符(|-> |=>);property的定义格式如下:

property name_of_property(参数列表);
  布尔表达式 或 sequence;
endproperty

property就是SVA中需要用来判定的部分,用来模拟过程中被验证的单元,它必须在模拟过程中被断言来发挥作用,SVA提供了关键字 assert 来检查属性,assert的基本语法是:

assertion_name: assert property(property_name) else $display("SVA error");
//else语句在断言失败时才会触发,可以不写

1、property 和 sequence

如果有两组信号的检查逻辑是类似的,可以使用property来复用,将两组信号作为参数传入。

	property ack_when_req_p(logic ack, logic req);
		@(posedge clk) disable iff(~rstn) ack |-> req;	// 可以复用的检查逻辑
	endproperty
	ast_cmd_ack:  assert property(ack_when_req_p(cmd_ack,  cmd_req));
	ast_data_ack: assert property(ack_when_req_p(data_ack, data_req));

如果有一段时序需要复用,可以使用sequence描述这段时序,然后在不同的property内复用。

	sequence req_ack_success_s;
		req && ack;	// req和ack同时为高
	endsequence
	property no_consecutive_p(logic ack, logic req);
		@(posedge clk) disable iff(~rstn)
		req_ack_success_s |-> ##1 not req_ack_success_s;	// 连续两次req和ack握手成功间隔必须大于1拍
	endproperty

property与sequence的相同和不同之处:

  1. 任何在sequence中的表达式都可以放到property中;
  2. 任何在property中的表达式也可以搬到sequence中,但是只有在property中才能使用蕴含操作符;
  3. property中可以例化其他property和sequence,sequence中也可以调用其他的sequence,但是不能例化property;
  4. property需要用cover/assert 等关键字进行实例化,而sequence直接调用即可;

三、辅助用法

1、generate

断言和verilog语句类似,只是多了一些独特的用法和关键词,也支持用generate语句批量产生断言。如果DUT内有一个module例化多份,一个断言规则在这些实例中都适用,那么这个功能就很好用。

	`define INST(num) tb_top.dut.inst``num	// 宏定义路径,用法详见文章 [宏定义日常用法](https://blog.csdn.net/Zwwang97/article/details/136601241)
	generate
		for(genvar num=0; num<4; num++) begin: inst_gen
			ast_ack_when_req: assert property(@(posedge clk) disable iff(~rstn)
				`INST(num).ack |-> `INST(num).req)	// 通过路径宏分别索引到各个inst,ack为高的同时req必定为高
			else `uvm_error("xxx", "ack when req is 0");
		end
	endgenerate

2、if else

如果对于cmd_req/ack信号,写命令是当拍回ack,但是读命令是下一拍,可以分开写成两个,也可以使用if else语句写成一个。

	ast_ack_when_req: assert property(@(posedge clk) disable iff(~rstn)
		cmd_ack |->
		if(cmd_type == WR)
			cmd_req
		else if(cmd_type == RD)
			$past(cmd_req))
	else `uvm_error("xxx", "ack when req is 0");

3、变量延迟

断言中只支持##1这种常数delay,但如果delay值是不确定的(如和寄存器配置值有关),可以用以下方法。

ast_ack_after_req: assert property(@(posedge clk) disable iff(~rstn)
	int delay;
	(req, delay = cfg_delay) ##0 (delay>0, delay=delay-1)[*0:$] ##0 (delay == 0) |-> ack)	// req为高后cfg_delay拍,ack必为高
else `uvm_error("xxx", "ast_ack_after_req fail");

总结

以上就是功能覆盖率中写断言时会用到的一些用法。

<think>好的,我现在要帮用户解决在SystemVerilog中进行时钟频率断言检查的问题。首先,我需要回忆一下SystemVerilog断言的基本结构,特别是并发断言相关的知识。用户的问题是关于时钟频率的检查,所以我需要找出如何用断言来测量时钟周期或频率。 首先,时钟频率的检查通常涉及到测量两个连续时钟边沿之间的时间间隔。在SystemVerilog中,可以使用`$time`函数来获取当前仿真时间。每次时钟上升沿到来时,记录下当前时间,然后计算与上一次的时间差,这个差值就是时钟周期。通过周期可以计算出频率,例如周期为10ns的话,频率就是100MHz。 接下来,考虑如何将这些时间差转化为断言。可能需要使用一个变量来保存前一次的时钟时间,然后在每次时钟边沿触发时更新这个变量,并检查当前时间与上次时间的差是否符合预期。例如,如果期望的时钟周期是10ns,那么时间差应该在9.9ns到10.1ns之间,允许一定的抖动。 然后,我需要考虑如何将这些逻辑写成SystemVerilog的并发断言。并发断言通常在`assert`语句中使用`property`关键字定义,并且需要指定触发条件,比如时钟边沿。可能的结构是:在每次时钟上升沿时,检查`$time - last_time`是否在允许的范围内,并更新`last_time`为当前的`$time`。这里需要注意的是,断言中的变量赋值可能需要使用非阻塞赋值或者适当的作用域,以避免仿真竞争条件。 另外,还要处理初始状态的问题。在第一次时钟边沿时,`last_time`可能还没有被初始化,这时候可能需要忽略第一次的检查,或者在复位信号有效时进行初始化。例如,在复位期间不进行断言检查,复位释放后开始监测时钟频率。 还需要考虑用户可能的实际应用场景,比如验证设计中时钟生成模块的正确性,或者检查在运行过程中时钟是否出现异常。这时,断言可以作为一个持续的监控工具,一旦时钟周期超出允许范围,立即触发错误报告。 接下来,我需要参考用户提供的引用内容。引用[3]提到了在`always_ff`块中使用时钟上升沿和复位信号来更新计数器,这可能与如何在时序逻辑中处理时间有关。引用[4]讨论了测试台中使用断言来驱动特定条件,这可能对如何结合测试环境来触发断言有帮助。引用[5]中的状态迁移示例可能对如何组织断言中的时序关系有参考价值。 现在,考虑编写一个具体的示例。例如,定义一个期望的周期值,比如10ns,允许的误差范围是±1%。断言需要检查每个时钟上升沿的时间间隔是否在9.9ns到10.1ns之间。代码可能如下: ```systemverilog real last_time; real cycle_time = 10.0; // 单位:ns real tolerance = 0.1; // ±10% 的误差 always @(posedge clk) begin real current_time = $time; if (last_time != 0) begin real measured_period = current_time - last_time; assert (measured_period >= cycle_time*(1 - tolerance) && measured_period <= cycle_time*(1 + tolerance)) else $error("Clock period violation: %0t ns", measured_period); end last_time = current_time; end ``` 不过,这样的代码是在过程块中使用立即断言,而用户可能需要使用并发断言。因此,需要将其转换为使用`property`和`assert`的结构。并发断言可能需要用`sequence`来捕捉时间差。但直接测量时间差在并发断言中可能比较困难,因为并发断言是基于事件而非连续的时间测量。 可能需要另一种方法:使用两个连续的时钟边沿之间的周期数来间接判断频率是否在允许范围内。例如,如果时钟频率应该在100MHz(周期10ns),那么在一个参考时间段内,比如1us,应该正好有100个时钟周期。但这种方法可能需要较长时间的监测,不如实时检测每个周期有效。 或者,使用`$past`函数结合时间差。例如,在每次时钟边沿,检查当前时间与上一次时间的差。但是,`$past`通常用于采样前一个周期的信号值,而不是时间变量。所以可能需要结合`$time`和变量存储来实现。 可能更合适的做法是,在断言外部的always块中计算时间差,并在断言中引用这个计算出的值。例如: ```systemverilog real last_clk_time; real period; always @(posedge clk) begin if (last_clk_time != 0) begin period = $time - last_clk_time; end last_clk_time = $time; end property check_clock_period; real expected_period = 10.0; // ns real tolerance = 0.1; // 10% @(posedge clk) (period >= expected_period*(1 - tolerance)) && (period <= expected_period*(1 + tolerance)); endproperty assert property (check_clock_period) else $error("Clock period out of range: %0t ns", period); ``` 不过,这里的问题是`period`变量是在always块中更新的,可能在断言触发时存在竞争条件。因为always块和并发断言都是基于时钟边沿触发,但执行顺序可能不确定,导致period的值可能不是最新的。需要确保在断言检查时,period已经被正确更新。 另外,并发断言中的条件表达式应该基于采样值,即在时钟边沿之前的稳定值。因此,可能需要将`period`的计算与断言触发同步,例如在时钟边沿之后立即计算,并在下一个时钟边沿检查。但这会导致一个周期的延迟,可能不符合实时监测的需求。 或者,可以使用立即断言在时钟边沿触发时检查时间差。例如,在always块中使用立即断言: ```systemverilog always @(posedge clk) begin if (last_clk_time != 0) begin automatic real current_period = $time - last_clk_time; assert (current_period >= 9.9 && current_period <= 10.1) else $error("Period error: %0t", current_period); end last_clk_time = $time; end ``` 这种方式可能更直接,因为每次时钟边沿都会立即计算并检查周期。不过,这属于立即断言,而用户可能需要并发断言的结构。但根据引用[3],立即断言在initial块中使用,而并发断言更适合持续监测。因此,用户的问题可能需要并发断言,所以需要找到合适的方法。 可能正确的并发断言写法是结合sequence和property,利用两次连续的时钟边沿之间的时间差。例如: ```systemverilog real start_time; sequence measure_period; real current; ($time, current = $time) ##1 ($time - current == 10.0, $display("Period is %0t", $time - current)); endsequence property clock_period_check; @(posedge clk) measure_period; endproperty assert property (clock_period_check); ``` 不过,这里可能需要更准确的时序控制。或者,考虑使用两个连续的posedge clk事件,并计算它们之间的时间差。例如: ```systemverilog property check_clk_period; real t1, t2; @(posedge clk) (1, t1 = $time) ##1 (1, t2 = $time, $display("Delta: %0t", t2 - t1)) |-> t2 - t1 == 10.0; endproperty assert property (check_clk_period); ``` 这里,`##1`表示下一个时钟边沿。在第一个posedge clk时记录t1,然后在下一个posedge clk时记录t2,并检查t2 - t1是否等于10.0。这种方法可以检测连续两个上升沿之间的时间差是否为预期的周期。但这样只能检查相邻两个边沿的时间,而无法检查每个周期是否都符合要求,因为每次检查的是连续两个边沿,可能中间的边沿被漏掉。 或者,可以每次在posedge clk触发时,检查与前一个边沿的时间差。这可能需要一个sequence来保存前一次的时间。例如: ```systemverilog sequence clock_measure; real t_prev; (@(posedge clk) (1, t_prev = $time)) ##1 (@(posedge clk) (1, $display("Period: %0t", $time - t_prev))); endsequence ``` 但如何在断言中比较这个时间差是否符合要求呢?可能需要在property中使用局部变量保存前一次的时间: ```systemverilog property check_clock_frequency; real t1, t2; @(posedge clk) (1, t1 = $time) ##1 (1, t2 = $time) |-> (t2 - t1 >= 9.9) && (t2 - t1 <= 10.1); endproperty assert property (check_clock_frequency); ``` 这里,`##1`表示下一个时钟边沿,所以每次断言触发时会检查两个连续上升沿之间的时间间隔。但这样会每隔一个周期检查一次,而不是每个周期都检查。例如,第一个边沿记录t1,第二个记录t2并检查间隔,第三个边沿又记录t1,第四个检查t2。这样可能无法检测到每个周期的偏差,比如如果第三个边沿的时间间隔异常,但第二个和第四个正常,可能不会被捕获到。 因此,可能需要更复杂的方法,每个周期都检查与前一个周期的时间差。这可能需要使用递归属性,或者在每个边沿都触发检查,而不仅仅是每隔一个边沿。 另一种思路是,在每个时钟边沿触发时,检查当前时间与上一次保存的时间差是否符合要求,然后更新保存的时间。这可以通过在property中使用局部变量来实现: ```systemverilog property check_clk_period; real t_prev, t_current; @(posedge clk) disable iff (reset) (1, t_prev = t_current, t_current = $time) |-> (t_prev == 0 || (t_current - t_prev >= 9.9 && t_current - t_prev <= 10.1)); endproperty assert property (check_clk_period); ``` 这里,每次posedge clk时,将t_prev更新为之前的t_current,t_current更新为当前时间。然后检查如果t_prev不是初始的0,那么时间差是否在范围内。这样,每个时钟边沿都会触发检查与前一个边沿的时间差。不过,在SystemVerilog中,property中的局部变量是否可以在每次触发时保持状态,可能需要确认。可能需要使用`local`变量,或者静态变量。 另外,变量作用域的问题需要注意,可能需要将t_prev和t_current声明在模块内部,或者在property外部的变量中。但这样可能会引入多驱动的问题,如果多个断言或过程块修改同一变量。 综合考虑,可能更可靠的方式是在模块内部使用变量来跟踪前一个时间,然后在断言中引用这些变量。例如: ```systemverilog real last_clk_time = 0; always @(posedge clk) begin if (last_clk_time != 0) begin real period = $time - last_clk_time; assert (period >= 9.9 && period <= 10.1) else $error("Clock period out of range: %0t ns", period); end last_clk_time = $time; end ``` 这虽然是一个立即断言,但可能更简单直接。不过用户可能希望使用并发断言的结构,这样可以利用SystemVerilog断言的优势,如覆盖率、形式验证等。 再参考用户提供的引用,引用[2]提到并发断言是在时钟事件信号之间编写的,引用[5]中的状态迁移示例可能涉及多个时钟周期的检查。因此,正确的并发断言可能需要使用`$past`函数或类似的机制来获取前一个周期的时间。 不过,`$past`通常用于获取信号的前一个值,而不是时间变量。因此,可能需要结合其他方法。 或者,可以这样设计:在每个时钟边沿,记录当前时间到变量prev_time,然后在下一个时钟边沿检查当前时间与prev_time的差。这可以通过以下方式实现: ```systemverilog real prev_time; always @(posedge clk) begin if (prev_time != 0) begin automatic real current_period = $time - prev_time; // 立即断言检查 assert (current_period >= 9.9 && current_period <= 10.1) else $error("..."); end prev_time = $time; end ``` 这种方式是可行的,但属于过程块中的立即断言。如果用户坚持要使用并发断言,可能需要更复杂的结构,例如: ```systemverilog sequence check_period_seq; real t1, t2; (1, t1 = $time) ##1 (1, t2 = $time) ##0 (1, $display("Period: %0t", t2 - t1)); endsequence property check_period_prop; @(posedge clk) check_period_seq |-> (t2 - t1 >=9.9 && t2 - t1 <=10.1); endproperty assert property (check_period_prop); ``` 不过,这里可能需要正确处理局部变量t1和t2的作用域。在sequence中定义的变量可能在每次sequence实例化时重新初始化,因此可能无法正确保存前一次的时间。 可能正确的写法是使用`local`变量,或者通过将时间保存在模块层次的变量中。例如: ```systemverilog real prev_time; sequence check_clk_period_seq; (1, prev_time = $time) ##1 (1, $display("Delta: %0t", $time - prev_time)); endsequence property check_clk_period_prop; @(posedge clk) check_clk_period_seq |-> ($time - prev_time >=9.9 && $time - prev_time <=10.1); endproperty assert property (check_clk_period_prop); ``` 但这样可能prev_time在sequence执行时被更新,而下一个周期时prev_time已经被覆盖,导致竞争条件。 或许,正确的做法是,在每次时钟边沿触发时,使用一个变量保存当前时间,并在下一个时钟边沿检查与前一次的差。这可以通过两个连续的时钟边沿来触发检查,例如: ```systemverilog property check_clk_freq; real t1, t2; @(posedge clk) (1, t1 = $time) ##1 @(posedge clk) (1, t2 = $time) |-> (t2 - t1 >= 19.9 && t2 - t1 <= 20.1); endproperty ``` 这里检查的是两个连续时钟边沿之间的时间差,但这样实际上检查的是两个周期的时间总和,这可能不符合需求,因为每个周期都应该被检查。例如,如果第一个周期是9ns,第二个是11ns,总和是20ns,这样断言不会报错,但实际上每个周期都偏离了预期。 因此,这种方法无法检测单个周期的偏差。所以,必须每个周期都检查与前一个周期的时间差。这就需要每个时钟边沿都触发一个检查,而不仅仅是每隔一个边沿。 总结来说,可能最可靠的方法是使用过程块中的立即断言,每次时钟边沿触发时计算与前一次的时间差,并进行检查。虽然这属于立即断言,但实现起来更直接,避免并发断言中变量作用域和时序的问题。不过用户可能需要并发断言的示例,因此需要找到如何在并发断言中实现这一点。 或许,正确的并发断言结构应该使用`$past`结合时间函数,但`$past`只能获取前一个周期的信号值,而无法直接获取时间差。例如: ```systemverilog property check_clk_period; real current_time, prev_time; @(posedge clk) (1, prev_time = current_time, current_time = $time) |-> (prev_time == 0 || (current_time - prev_time >=9.9 && current_time - prev_time <=10.1)); endproperty assert property (check_clk_period); ``` 这里,每次posedge clk时,prev_time被赋值为current_time的前一个值,current_time被更新为当前时间。然后检查时间差是否符合要求。但问题在于,在并发断言中,局部变量current_time和prev_time是否在每次属性触发时保持其值。可能需要使用静态变量或者在模块中声明这些变量。 如果这些变量是在property内部声明的,那么每次属性触发时都会重新初始化,导致无法保存之前的状态。因此,可能需要在模块级别声明变量: ```systemverilog real prev_clk_time = 0; real curr_clk_time; property check_clk_period; @(posedge clk) (1, curr_clk_time = $time) |-> (prev_clk_time == 0 || (curr_clk_time - prev_clk_time >=9.9 && curr_clk_time - prev_clk_time <=10.1)); endproperty assert property (check_clk_period); always @(posedge clk) begin prev_clk_time <= curr_clk_time; end ``` 这样,每次posedge clk时,curr_clk_time被赋值为当前时间,然后断言触发时检查与前一次prev_clk_time的差。而prev_clk_time在always块中被更新为curr_clk_time的值。不过,这里可能存在时序问题,因为always块中的非阻塞赋值可能导致prev_clk_time在断言检查之后才更新。例如,在第一个posedge clk时,curr_clk_time被设置为当前时间,prev_clk_time还是0,断言检查通过。然后,在always块中,prev_clk_time被更新为curr_clk_time的值。在下一个posedge clk时,curr_clk_time再次更新,断言检查与前一次的值是否符合要求。这样可能正确。 但需要注意,`prev_clk_time`的更新是在always块中使用非阻塞赋值,可能在当前时间步的末尾才更新,因此在断言触发时,prev_clk_time的值仍然是前一个周期的值。这可能正确,因为非阻塞赋值的特性就是如此。 不过,这样设计可能较为复杂,容易出错。因此,可能更简单的方式是使用过程块中的立即断言,每次边沿触发时直接计算和检查,如之前的示例。 综合考虑用户的需求和示例的准确性,可能给出两种方法:一种是过程块中的立即断言,另一种是并发断言的结构,可能结合模块变量。需要确认哪种方法更符合SystemVerilog的最佳实践。 另外,用户提供的引用[3]中提到了在always_ff块中使用断言,这可能支持在过程块中使用立即断言的做法。因此,可以建议用户使用过程块中的立即断言来检查时钟频率,这种方法简单有效,代码也容易理解。 不过,用户的问题明确提到要使用SystemVerilog断言,可能更倾向于并发断言的结构。因此,需要再次尝试构造正确的并发断言。 或许,正确的并发断言写法如下: ```systemverilog // 模块中定义变量保存前一次时间 real prev_time = 0; property check_clock_period; real current_time, delta; @(posedge clk) (1, current_time = $time, delta = current_time - prev_time) ##0 (1, prev_time = current_time) |-> (prev_time ==0 || (delta >=9.9 && delta <=10.1)); endproperty assert property (check_clock_period); ``` 这里,每次posedge clk触发时,计算当前时间和prev_time的差,然后更新prev_time。断言的条件是如果prev_time不为0(即不是第一次),则检查delta是否符合要求。但这里的问题在于,在并发断言中,赋值操作可能需要考虑其执行顺序和时序。此外,`##0`表示同一时间步中的延续,所以可能正确执行顺序是:先计算current_time和delta,然后更新prev_time,最后检查条件。 不过,这样的写法可能在仿真器中不被支持,或者在时序上存在问题。例如,prev_time的赋值是否在检查delta之后才生效。可能需要将prev_time的更新放在检查之后,或者使用非阻塞赋值。 另一种方式是将prev_time的更新放在另一个always块中: ```systemverilog real prev_time = 0; always @(posedge clk) begin prev_time <= $time; end property check_clock_period; real delta; @(posedge clk) delta = $time - prev_time |-> delta >=9.9 && delta <=10.1; endproperty assert property (check_clock_period) else $error("..."); ``` 这里,prev_time在always块中每个posedge clk更新为当前时间。然后,在断言中,计算当前时间与前一个prev_time的差。由于prev_time是使用非阻塞赋值更新的,它会在当前时间步的末尾更新,因此在断言触发时,prev_time仍然是前一个周期的值,这样delta就是当前时间与前一个周期时间的差,即正确的周期值。 例如,假设prev_time在时刻10ns被更新。下一个posedge clk在20ns到来,此时prev_time仍然是10ns(因为非阻塞赋值在always块中),所以delta = 20 -10 =10ns,符合预期。因此,这种方法是正确的。 这种方法的优点在于使用了非阻塞赋值,确保prev_time在每个时钟边沿后更新,而断言在下一个边沿触发时使用正确的prev_time值。因此,断言中的delta计算是正确的周期时间。 因此,正确的示例代码应该是这样的结构: 在模块中定义prev_time变量,使用非阻塞赋值在always块中更新,然后在并发断言中检查delta是否符合要求。例如: ```systemverilog module clock_checker(input clk, input reset); real prev_time; always @(posedge clk or posedge reset) begin if (reset) begin prev_time <= 0; end else begin prev_time <= $time; end end property check_clk_freq; real delta; @(posedge clk) disable iff (reset) (1, delta = $time - prev_time) |-> (prev_time ==0 || (delta >=9.9 && delta <=10.1)); endproperty assert property (check_clk_freq) else $error("Clock period error: %0t ns", delta); endmodule ``` 这样,每次时钟边沿到来时,prev_time被更新为前一次的$time值,而当前的delta是当前时间减去prev_time(即上一个时钟边沿的时间),得到当前周期的长度。然后断言检查这个delta是否在允许范围内。在复位时,prev_time被重置为0,第一次时钟边沿后,prev_time被设置为第一个边沿的时间,第二次边沿时计算delta为第一个到第二个边沿的时间差,此时prev_time不为0,检查delta是否正确。 这种结构应该能够正确工作,并且符合并发断言的使用方式。用户可以根据需要调整周期和误差范围,并处理复位信号。 总结步骤: 1. 使用always块和非阻塞赋值记录每个时钟边沿的时间。 2. 在并发断言中,计算当前时间与记录的上次时间的差。 3. 检查时间差是否符合预期频率对应的周期范围。 4. 处理复位信号以初始化时间记录。 这样,用户就可以在SystemVerilog中实现时钟频率的断言检查了。同时,需要考虑可能存在的初始状态和复位条件,避免误报。</think>在SystemVerilog中检查时钟频率的常用方法是测量连续时钟边沿之间的时间间隔,并通过并发断言验证其是否符合预期范围。以下是具体实现方法: ### 时钟频率检查实现 ```systemverilog module clk_freq_checker( input logic clk, input logic reset_n // 异步复位 ); real prev_time = 0; real period; // 记录前一时钟边沿时间 always_ff @(posedge clk or negedge reset_n) begin if (!reset_n) begin prev_time <= 0; end else begin prev_time <= $time; end end // 并发断言检查周期 property check_clock_period; real delta; @(posedge clk) disable iff (!reset_n) (1, delta = $time - prev_time) |-> // 计算时间差 (prev_time == 0 || // 跳过初始状态 (delta >= 9.9 && delta <= 10.1)); // 10ns ±1% 误差带 endproperty assert property (check_clock_period) else $error("[%0t] 时钟周期异常:%0t ns", $time, delta); endmodule ``` ### 关键实现细节 1. **时间记录机制**: - 使用`always_ff`块记录前一时钟边沿的仿真时间`$time` - 采用非阻塞赋值确保时序一致性[^3] 2. **断言工作流程**: $$周期检测公式:T_{measured} = t_{current} - t_{previous}$$ - 当`prev_time=0`时跳过初始状态检查 - 允许±1%的误差带(示例中10ns周期允许9.9-10.1ns) 3. **复位处理**: - 通过`disable iff`在复位期间禁用断言[^5] - 复位信号恢复后重新开始记录时间 ### 扩展应用 1. **动态频率检测**: ```systemverilog // 添加参数化配置 parameter real EXPECTED_PERIOD = 10.0; parameter real TOLERANCE_PCT = 1.0; // 修改断言条件 (delta >= EXPECTED_PERIOD*(100-TOLERANCE_PCT)/100 && delta <= EXPECTED_PERIOD*(100+TOLERANCE_PCT)/100) ``` 2. **多时钟域检查**: ```systemverilog // 例化多个检查器 clk_freq_checker #(.EXPECTED_PERIOD(10)) clk1_check(apb_clk, reset_n); clk_freq_checker #(.EXPECTED_PERIOD(20)) clk2_check(axi_clk, reset_n); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值