这个写的不好,直接伊移步到后面的一篇吧:
【system verilog】time-slot,仿真的竞争与冒险,对齐与采样-CSDN博客
前言
时间片time-slot(后简称ts)是EDA工具进行仿真进程中的抽象时间单位,该时间点内所有线程被划分为相应的优先级进行调度。如果线程在同一ts内被调度,从外部看他们仿佛属于同一时间点“并行”执行的,但是实际上是有先有后,因为软件行为中不存在绝对的并行。ts存在的主要价值是解决仿真中的竞争与冒险,使仿真行为尽量与实际电路行为保持一致。
归结起来一句话:通过一个ts中发生的行为,可以认为是实际电路中同一时刻并行完成的,而在仿真中是有先后顺序调度的。
不过ts机制也不能完全消除竞争与冒险问题,还是要配合良好的代码习惯的。之前零零散散学习了很多time slot相关知识,今天对所学进行梳理。
time-solt中的概念
SV标准中的仿真调度机制有一个很长的图,不过我觉得里面画的太多了不是很便于理解同时没必要掌握的这么细致(比如里面的pre- post- 时间点我只在研究断言的时候关注到过),所以我就手画了一个更加简单明了的调度图便于学习,ts图示如下:
结合相关资料,可以对时间区域大体划分如下:
域名 | 作用 |
preponed | 时间片入口,断言采样时间 |
active/inactive/NBA | module中代码执行时间 |
observed | 断言检查时间 |
re-active/re-inactive/re-NBA | program中代码执行时间 |
postponed | 时间片出口 |
下面做一下具体说明。
preponed区域:
时间片入口,该区域内各线程内信号的数值与上一个ts的postponed域值保持一致并且会保持稳定;
同时是断言中信号采样区,故而断言采样到的值一定是旧值;
同时也是module中各线程信号的旧值,后续判定信号是否有跳变就会用到这个域的值;
active区域:
执行module中的assign/initial/always线程;
阻塞赋值直接在本区域完成,非阻塞赋值在本区域开始并被推入NBA域(no-blocking)域等待赋值完成,带有#0时延的线程被推入in-active域(小于最小仿真粒度的#延时四舍五入为#0后也应该推入in-active域,如1ns/1ps时,#0.4ps会被四舍五入为#0);
如果若干相关操作都在active域执行,那么会按照代码顺序来执行(如begin end块中的代码);
需要注意的是不仅仅RTL代码可以放置在module,验证环境也可以放置在module中,如果这样做的话,验证环境的线程执行时间与RTL代码一致,因此有些工具书上说RTL代码在active/inactive/NBA区域执行,验证环境在re-active/re-inactive/re-NBA域中执行是不准确的;
in-active区域:
#0延时的线程会在该区域执行,因此从执行时间上看会落后其他同一时间点的线程;
如果有非阻塞赋值则推入NBA域;
NBA域:
active域和in-active域的线程都处理干净了,就会走到NBA域;
NBA域最大的动作就是完成非阻塞赋值的操作使赋值行为生效,那么就是如果“环境”采样module信号是在NBA之后,那么采样到的就是“新值”;
如果NBA域的变化触发了别的线程比如sig在NBA域完成了跳转,其他代码@sig,那么就会推回到active域执行;
observed域:
active/inactive/NBA的线程全部执行完毕后进入observed域,observed域隔离开了module时间片和program时间片,避免了验证环境与RTL线程产生竞争和冒险;
断言在observed域执行匹配和判断,注意匹配所使用的是信号的“旧值”;
interface和program的线程在该时间点进行采样,采样到的是新值,例如使用@bus_if.mon_cb进行对齐采样,mon_cb的上升沿在observed域才会被@到,因此会采到其他信号跳变后的新值,后面会有实例看到;
re-active域:
program中线程开始执行,program中的re-active域就相当于module中active域一样,这里的阻塞赋值立即执行,非阻塞赋值被推如re-NBA域;
如果该区域对module中的代码有赋值操作,触发了module域中的线程,那么被激活的线程会再次被迁移到active域中;
同样如果之后program的中代码被后续操作触发,线程会被迁移到re-active域来执行;
re-inactive域:
program中#0延时的线程在此区域执行,如果有非阻塞赋值则推入re-NBA域;
re-NBA域:
program环境中非阻塞赋值的生效区域,如果该区域触发了其他线程,那么被激活的module线程会被迁移到active域,program线程会迁移到re-active域;
postponed域:
所有的线程都处理结束后就会进入该区域,进入该区域后所有信号保持稳定且与下一个ts的preponed域值一致;
该域也是外部PLI/DPI的callback点;
好的,ts中各个区域就是这些了,这样看其实也没有那么复杂。。。。。
再来看一下采样时间点的事情,目前看起来除了顺序执行的非阻塞赋值会立即生效外,其他线程大体都会需要一个采样-生效的过程,而这个采样点一般是preponed域、observed域。
常见场景分析
always@clk阻塞赋值与非阻塞赋值
阻塞赋值在always块中顺序执行,在active域中执行且赋值立即生效;
非阻塞赋值在时序always块中“并行执行”,即preponed域采样,在NBA域赋值生效;
说些题外话关于阻塞语句和非阻塞语句,线程遇到阻塞赋值语句时会被阻塞住,直到赋值完成后该线程才会继续执行往下走;而线程遇到非阻塞赋值时,不会被阻塞会继续向下执行,赋值行为被暂时挂起,直到线程遇到阻塞语句赋值行为才会执行;这样做的目的时为了保证多个赋值在同一时刻执行,不会受先后顺序的影响;
典型的阻塞语句包括#、@、wait,但是不包括#0;典型的非阻塞语句比如fork-join_none;
assign赋值与always@(*)赋值
assign和always@(*)在仿真中的结果是一致的,综合出的电路也是一致的(如果有什么场景是不同的,请不吝告知);
以always@(a,b,c)为例,进入当前ts后在preponed域采样之后直接到observed域等待是否有后续触发;a,b,c三个信号在active域~NBA域各种被触发赋值,最后各线程稳定在observed域;
always@(a,b,c)线程检查observed采样值是否和preponed域采样值有变化,如果有变化那么always@(a,b,c)线程会被推回active域执行;
使用interface.clk采样和使用rtl.clk采样
interface和program的采样操作都是发生在observed域,那么根据验证环境搭建在module中还是program中,对非阻塞采样会有如下特性:
验证环境在module中,使用@interface.cb采样,会采样到跳变后的“新值”(observed域的值);
验证环境在module中,使用@rtl.clk采样,会采样到跳变前的“旧值”(preponed域的值);
验证环境在program中,无论哪种采样方式,都会采样到跳变后的“新值”;
若干实例探究
案例虽然我已经都测试过,但是必须在我自己虚拟机vcs里复现一下,因为发现modelsim跟vcs的对于某些行为的解释是不太一致的,这个之后也记录下,不过既然vcs是老大就按vcs的解释走吧。