[sv] region & timeslot

5 篇文章 1 订阅

前言
 

这篇文章主要讨论的是数字芯片验证领域,或者说仿真器仿真行为,这一范畴内的时序竞争与冒险。从关联性来讲,内容贴近这一篇博客:

https://blog.csdn.net/moon9999/article/details/102983963

不过因为最近又对这一内容有了更加深刻的领悟与认识,也意识到之前自己的理解是有一定误区的,所以希望借此记录,与大家分享。

本文的很大一部分内容来源自IEEE system verilog标准第四章“Scheduling semantics”,其余来自个人实验与其他相关资料。

真实时间与仿真时间
 

一般在功能仿真中,会涉及到两个时间观念:真实时间(或称之为CPU时间)与仿真时间。仿真时间很好理解,例如RTL电路中我们平时总说的第100个时钟周期、第100ns等时间概念均为仿真时间。

CPU时间则是仿真器真实花费的时间,例如我们仿真了50ms仿真时间,如果电路规模很大的话,可能会花费几小时甚至几天的CPU时间来完成。因此我们可以认为CPU时间就是真实世界的时间。

仿真过程中的时间是由一个个的time-slot构成的,由阻塞性的事件(event)与线程(thread/process)推进仿真时间前进。

事件与进程
 

system verilog的代码行为是由一个个离散事件组成,运行sv时也就是在执行一个个的事件与线程。值得注意的是,仿真器中线程与事件的执行是串行方式,而真实的RTL电路代码的执行方式是并行执行,仿真器需要通过调度串行事件来模拟芯片真实的并行行为。因此,为了模拟贴近真实电路行为,明确仿真环境行为,避免竞争冒险与采样不确定等危机,SV标准划分了明确的事件调度与代码执行区间。

关于进程,通常我们写下的每一句执行性的verilog代码和sv代码,在仿真器看来都是一个进程(更多时候将有时序或时间推进的行为成为进程/线程,因此function一般不被称作进程)。典型的进程包括:

Initia, always, always_comb, always_latch, always_ff, assign, task, 其他赋值语句等。

事件在标准中包括两种,update event和evaluation event,具体解释如下:

简单来讲就是任何一个数值/信号(sv中将数据划分为net型与variable储值型,这个另外讨论吧)的变化都是一个update event,对该变化敏感的若干进程感知该变化后的执行过程是一个evaluation event。因此可以认为进程也是事件的一个子集。

需要注意一点,对同一update event敏感的若干进程,在执行时必然时串行执行,但是串行执行顺序标准中没有做规定,不同仿真器可以做出自己的安排。

当然我们没有必要咬文嚼字,明确我们写下的执行性代码被编译器转化为一个个事件与进程,在不同时间点(time-slot)和不同触发条件下执行即可。

仿真过程
 

仿真时间由无数的time-slot构成的,每个time-slot中由划分了若干个区域,每个区域执行标准中规定的相应进程。一次完整的仿真过程也就是把全部time-slot执行完成的过程。这里贴以下标准中的伪码,做一下简单的分析,当然之后这段伪码我们还会再见。

一次完整的仿真器执行的功能仿真经历了什么呢?

仿真时间T归零,仿真开始;
初始化所有的储值单元和电路单元;
调度并执行所有初始化阶段的事件在ts 0时刻;
检查是否所有的time-slot都已经完成,如果还有若干time-slot需要执行,那么推进时间到第一个待执行的time-slot,并且设定仿真时间T为这个time-slot的时刻;举例,如果一个仿真环境只有一句打印,那么仿真会直接在T=0时刻结束,因为0时刻时候就已经没有time-slot待执行了。如果环境中有一句#10ns $display(),那么在0时刻后仿真器会发现还有一个time-slot待执行,因此将仿真时间推进到10ns执行这句打印,之后结束仿真;
执行T时刻的time-slot中的所有event(事件和进程),直到该time-slot内的全部事件执行结束,返回第4条。
直到将所有非空/待执行的time-slot执行结束,仿真器才会结束仿真过程。

Time-slot
于是乎,事情的重点又来到了time-slot。

Time-slot对于硬件RTL电路而言,就是一个时刻/时间点,但是对于软件仿真器而言,这一个时刻内发生了很多调度与执行进程。通过调度和组织执行进程的执行,将软件行为尽可能的模拟贴近真实硬件行为。

那么我们先将time-slot理解为仿真过程中每一个有事情要做的时间点即可,之后还是根据SV标准进行深入分析。

SV标准中将一个time-slot划分为了17个区域,分别是:

如果将这些regions展开在一个time-slot,就构成了下面这个图:

如果想深入研究这幅图的话,可以参考SV标准,在这里我们还是看简化图比较好,不是太影响理解。下面时画的简化time-slot图:

通过简化图我们可以发现,time-slot其实是一个具有对称美感的非常简单的结构,我们再复习一下time-slot中若干regions的功能;

Preponed域


 

一个time-slot的入口,也是当前time-slot的采样点,在进入当前的time-slot时立刻采样信号目前的实际值。

关于采样值与实际值多说一句,什么是采样值呢?就是在仿真的特定时刻(一般而言就是preponed时刻,当然是一般而言),将信号的数值进行保存以便之后使用。因此我们在环境中使用的信号都是采样值,值得注意的是,采样值在仿真中的大部分时刻与实际值是有差异的,但是不用担心只要写法规范这个差异不会造成问题。

以图示表示采样值与电路实际值关系如下图,可以看出在环境在20ns处对实际信号进行了采样,那么在20ns~45ns过程中环境中使用该信号时,使用的值都是A。环境中time-slot的采样点就是preponed域。

Active域
 

在active域内以任意顺序执行当前active状态的event,简单来讲我们常见的module进程比如assign赋值等都在active域内执行。

阻塞赋值直接在本区域完成,非阻塞赋值在本区域开始并被推入NBA域(no blocking region)域等待赋值完成,带有#0时延的线程被推入in-active域(小于最小仿真粒度的#延时四舍五入为#0后也应该推入in-active域,如1ns/1ps时,#0.4ps会被四舍五入为#0);

进程内部的操作时顺序完成的,例如begin-end块内的代码;进程间的顺序没有规定,可以以随意顺序执行,例如若干assign赋值,fork-join块中的并行线程;

需要注意的是不仅仅RTL代码可以放置在module,验证环境也可以放置在module中,如果这样做的话,验证环境的线程执行时间与RTL代码一致,因此有些工具书上说RTL代码在active/inactive/NBA区域执行,验证环境在re-active/re-inactive/re-NBA域中执行是不准确的;

In-active域


 

所有的#0delay线程会被推进到in-active域,等待active域(本轮)中的线程执行完成再执行in-active域中的线程。

NBA域


 

非阻塞赋值<=在NBA域中生效,因此如果在NBA域之后采样信号,采样得到的是赋值后的新值。

Observed域


 

active/inactive/NBA的线程全部执行完毕后进入observed域,observed域隔离开了module时间片和program时间片,避免了验证环境与RTL线程产生竞争和冒险;

断言在observed域执行匹配和判断,注意匹配所使用的是preponed采样的信号“旧值”;

下面呈镜像对称的re-域是program中的执行域,不再重复。那么接下来我们看下一个time-slot是如何调度这些时间regions的。

Ts调度执行
 

先把刚刚看的那幅图搬出来,我们看到在仿真器返现有time-slot待执行时候,就会跳转到这一时间点,并且执行execute_time_slot(T)来执行这一ts内的所有域和域内的进程。


 

execute_time_slot(T)的伪码如下图,我们来学习一下。

执行preponed中进程和pre-active中进程,进程主要是采样和DPI等操作,注意一个ts中这两个域的线程只会执行这一次;
检查active~pre-postponed regions中是否有非空/待执行的,如果有则开始执行,注意这里的进入执行是while语句,也为这regions不是线性执行的,而是反复检查是否regions全部执行结束,只要有未空regions就会跳回继续执行;
可以发现第一层while(4行处)的下面是两段while循环,泾渭分明的将module regions(active~post-observed)和program regions(re-active~post-re-NBA)分开,也就是说program regions中的进程一定是在module regions中进程执行的彻彻底底全部执行清之后,才会开始执行;
看第二层module regions的while循环(5行处),先执行active域中的进程,然后检查active~post-observed regions中是否有进程待执行,如果有就把这些进程放回到active region执行。
因此,module regions具体执行效果就是:a. 执行active域所有线程,遇到#0delay先挂起放到in-active去,遇到<=非阻塞赋值先把右面的值记下来,进程挂起到NBA域去;b. active内进程处理干净了,看下in-active内是否有挂起来的进程,如果有就把这些进行取出来到active,再进行一下active执行这些进程,如此反复直到active~in-active内都没有进程待执行了;c. 直到active和in-active中的进程全部执行干净,NBA域内非阻塞赋值的进程会生效,如果某一进程对NBA域内的update event敏感,则会在此时被提至active内;d. active~pre-observed域内的进程反复几轮执行完成后,module regions的进程执行结束,进入observed域,之后进入program regions,至此第二层循环(5行处)完成;但是注意这并不意味该time-slot不会再次回到module regions执行,仔细观察伪码和示意图可知,如果module中有进程对program regions event敏感,进程再次被提至active域等待执行;
module regions之后time-slot推进到re-active~post-re-NBA regions循环执行进程,执行完成后检查所有regions中是否还有进程待执行,如果有则回到while循环的开始重复此过程;
 

可以看下如下几段代码:

always @(posedge clk) begin
    if(rst_n == 1'b0)begin
    data_out_vld <= 1'b0;
    end
    else begin
    data_out_vld <= data_out_vld_ff1;//进程1
    end
end
    
assign data_out_vld_back = data_out_vld;//进程2
//进程2对进程1敏感,会在NBA域之后被置于active域内待执行
assign data_out_vld_back = data_out_vld;//进程1 //module region
 
while(1)begin
    @clk;
    data_out_vld = 1;//进程2 //program region
end
//进程1会在进程2在active域内执行时候被置于active内,等待执行
 

两个典型场景
 

非阻塞赋值
 

always@(posedge clk)begin
    sig_ff <= sig;
end
 

@(posedge clk)在时钟上升沿时发现有进程待执行,因此执行该time-slot regions。采样发生在ts的preponed region,因此采样到sig上一个ts postponed region的值也就是就值A,之后再active执行进程sig_ff <= sig,非阻塞进程在NBA域生效。

电平赋值
 

assign c = b + 1;
 
always@(b,c)begin
    a = b + c;
end
 

信号b的跳变是update event,执行后所有对其敏感的进程会放置在相应的region,因此c=b+1和a=b+c进程都置于active region。两个进程随机顺序执行,因此从编译器角度看两种处理都是合乎规范的:c = b + 1先生效,a = b + c后生效,如下图1;a = b + c先生效,c = b + 1后生效,c值update再次将a = b + c至于active使其再生效;

两种行为都是符合标准的,看编译器选择,当然这里一般而言对仿真没有影响,真实电路行为也是明确的。

interface的采样
 

interface中有一个非常非常重要的东西,就是clocking block。clocking block里面我们通常会设置input skew和output skew。如设置default input #1ps,那么mon clocking block中的采样值是时钟上升沿前1ps的采样值,这样就避免了可能的取值不定。如设置default output #1ps,那么drv clocking block会在时钟上升沿后1ps去驱动interface.data(即RTL信号),避免了可能存在的驱动冒险。

当然这两个值不设置的话,默认input skew应该是#1step,即取值在当前time-slot之前的一个step(即上一个时间片的postponed域,由于前一个ts的postponed域采样值与当前ts preponed域值一样,可认为默认情况下interface clock block采样发生在该time-slot preponed域),默认output skew记不清楚是不是#1step了。

关于clocking block,我们主要明确几点:

无论在任何位置@clocking block(例如@bus.mon),该时钟对齐发生的时刻都是observed域,因此@bus.mon之后,module所有信号的值都已经update完成,可以看到“新值”;
bus.mon.sig采样操作发生在preponed域之前(input skew >= 1step),但是更新到bus.mon.sig的行为发生在active~observed之间,这意味这在module环境中,@rtl.clk之后取bus.mon.sig数据进程(进程位于active)和bus.mon.sig更新进程存在竞争,不建议使用;
无论在任何位置使用@bus.mon 取bus.mon.sig,使用的都是preponed(或更早,例如default input 1ps)的值,即跳变前的“旧值”。
 
验证环境中对齐和采样
 

验证环境搭建于program中
 
先上结论
 

无论@rtl.clk或是@interface.mon(clocking block),使用rtl.sig(或interface.sig),会使用到的是新值,原因是program位于program regions,此时rtl代码已在module regions完成更新;
无论@rtl.clk或是@interface.mon(clocking block),使用interface.mon.sig,会使用到的是旧值,原因是interface.mon.sig在preponed region或更之前完成的更新;
环境搭建于program中
@rtl.clk后使用信号    rtl.sig    取用的是“新值”
if.mon.sig    取用的是“旧值”
@if.mon后使用信号    rtl.sig    取用的是“新值”
if.mon.sig    取用的是“旧值”
 
代码验证
 

观察打印即可得到结论;

验证环境搭建于module中
 

先上结论
 

@rtl.clk,使用rtl.sig(或interface.sig),会导致冒险不一定采样到新值或旧值,原因是rtl.sig的更新可能位于active region,环境中使用rtl.sig也位于active region,两个进程不确定哪一进程先执行;但是如rtl.sig是纯寄存器Q端信号(位于<=左侧的被赋值信号,),由于赋值生效于NBA域,此时可以采样到旧值;
@rtl.clk,使用interface.mon.sig存在冒险,原因是interface.mon.sig在preponed region采样,采样到相对本拍的“旧值”,而生效于active~observed域;
@interface.mon(clocking block),使用rtl.sig(或interface.sig),使用到的是旧值,因为@interface.mon对齐行为发生在observered region,rtl信号已在之前的module regions完成更新;
@interface.mon(clocking block),使用interface.mon.sig,原因是interface.mon.sig在active~observed域完成的更新;
环境搭建于module中
@rtl.clk后使用信号    rtl.sig    存在冒险,取值不确定,不推荐
rtl.sig,位于<=左侧    取用的是“旧值”
if.mon.sig    
存在冒险,取值不确定,不推荐

取用的可能是“旧值”/“更旧的值”

@if.mon后使用信号    rtl.sig    取用的是“新值”
if.mon.sig    取用的是“旧值”
 

代码验证
 

@if.mon,rtl.sig,取用新值,因此835时刻打印出来1;

@rtl.clk,rtl.sig(寄存器Q端),取用旧值,因此845时刻打印出来1;

@if.mon,if.mon.sig,取用旧值,因此845时刻打印出来1;

@rtl.clk,if.mon.sig,可能取用旧值或更旧的值,因此855时刻打印出来1;

@rtl.clk,#0 if.mon.sig,855时刻打印出来1,说明if.mon.sig的更新在active之后。

小彩蛋
 

前面我们讲了,clocking block采样默认是在preponed更前,因此if.mon.sig一定是落后if.sig一拍的,如下图:

那么有没有办法强行让if.mon.sig和if.sig对齐到同一拍呢?答案是可以的,只要把input skew设置为#0,此时if.mon.sig采样发生在observed域,因此会采样到rtl.sig(也就是if.sig)更新后的值:


————————————————
版权声明:本文为CSDN博主「尼德兰的喵」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/moon9999/article/details/105617702

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值