posedge(下降沿posedge)

来源:内容来自知乎,作者王君实,谢谢。要深入理解Verilog,必须正视Verilog语言兼具硬件和软件特性的事实。在目前的教学过程中,无论是教师还是教材都过于

来源:内容来自知乎,作者王君实,谢谢。

要深入理解Verilog,必须正视Verilog语言兼具硬件和软件特性的事实。在目前的教学过程中,无论是教师还是教材都过于强调Verilog语言的硬件特性和可综合特性。Verilog语言的行为语法只是作为语法设置来介绍,忽略了Verilog语言的软件和仿真特性。初学者无法理解Verilog语言的行为语法(进程块、赋值和延迟)背后的设计思想。本文试图从模拟器的角度来解读Verilog语言的语法规则。

Verilog语言的“细微之处”

在集成电路设计过程中,Verilog源文件有两个主要功能:综合和仿真。在图1中,用数字① ② ③ ④标注的位置可以用Verilog作为设计的描述方法。

综合工具读入源文件,并通过综合算法(如DC)将设计转换成网表。综合的能力需要Verilog语言描述信号的各种状态(0,1,x,z),信号和模块之间的联系(实例化)以及模块的逻辑(赋值和各种运算符)。

仿真器读取源文件并生成可执行程序来模拟硬件的行为,例如VCS。仿真的特点要求Verilog语言具有软件特性,定义各语句(延迟语句)的执行语义和顺序。同时,软件特性使得Verilog语言更加灵活,具有丰富的行为级模拟能力(条件分支、循环等。).

posedge(下降沿posedge)图一。集成电路设计过程

为了满足综合和仿真的双重要求,Verilog语言的语法规则必须同时满足硬件和软件的特点。在编写Verilog代码时,不仅要从硬件的角度考虑这段代码会被转换成什么样的硬件电路,还要从软件的角度考虑这段代码在模拟器中会有怎样的表现。如此日复一日,隐隐有种精神分裂的感觉。Verilog代码与硬件电路的关系在大量书籍中已经有充分的论述。本文重点讨论如何从软件的角度理解Verilog。

从软件的角度理解Verilog,并不是把Verilog看成一个可执行的程序,试图理解每条语句对应的语义。如果你这样做,就会陷入更深的误区,无法自拔。很多句子规则和规定会变得混乱和不可理解。在试图从软件的角度理解Verilog语言之前,我们必须坚定地认为Verilog是一种硬件描述语言。

从软件的角度理解Verilog的本质是理解Verilog语言在软件模拟器中的行为。Verilog语言本身无法执行。实际上,Verilog提供了一套描述硬件电路行为的规范。该标准设计与模拟器设计兼容。模拟器根据Verilog文件生成可执行的模拟程序。这个模拟程序是真正的软件程序。

在目前的教学过程中,已经充分强调了Verilog的硬件特性。在学习Verilog的第一天,很多同学都会受到老师的教育:Verilog描述的是硬件,但Verilog不是软件。这种强调的目的是避免将Verilog语言与纯软件语言(C、java、python等)混淆。).但同时也是矫枉过正。Verilog的软件特性只介绍是什么,不介绍为什么,几乎不涉及模拟器的内容。在讲授Verilog语言时,我们只能从语义的角度模糊地介绍Verilog的各种语法规则。无论是数字电路还是集成电路设计课程,都不会向学生介绍模拟器的基本结构和运行机制。

此外,电子设计自动化(EDA)工具的研究、设计方法学和设计过程的探索和进化在中国长期被忽视,人们习惯于“自己拿”的方式。最终导致国内EDA专业人才严重短缺。老师普遍不清楚工具背后的软件机制,只有避开Verilog的软件属性,才能避免课堂上的尴尬。在这样的教学方式下,学生很难对Verilog语言甚至HDL语言建立正确的理解和知识体系。

模拟器的基本架构

Verilog语言真的不是可执行语言。图2显示了用Verilog源文件进行仿真的过程。大多数模拟器都遵循这一理念,如VCS、伊维洛格、ModelSim、Vivado和Quartus。首先准备好Verilog源文件和一些Verilog库文件(标准单位等。).仿真器接收这些Verilog文件,并将其转换成可执行的仿真源文件(C/C++等。).在这个过程中,模拟器解析Verilog文件的语法结构,根据Verilog语法的规范,将语法结构转换成模拟器中的事件响应函数或代码段。这些函数和代码段与模拟器框架源文件一起成为可执行模拟程序的源文件。接下来,编译这些源文件,得到可执行的仿真程序。VCS和伊维罗格可以看到生成的可执行文件。ModelSim、Vivado和Quartus使用GUI来管理设计过程,从而屏蔽了这个可执行文件,使其对用户透明。用户可以在项目中找到生成的可执行文件。最后,运行可执行仿真程序进行软件仿真。

posedge(下降沿posedge)图2从Verilog源文件到可执行仿真程序的流程

可执行仿真源文件和仿真器框架源文件一般是不可见的。但是,生成可执行仿真源文件的代码可以在开源软件(如iVerilog)中找到。

仿真程序通常采用基于事件的仿真体系结构。这个模拟体系结构的核心是事件队列。一系列事件根据事件的响应时间排列在队列中。响应时间相同的事件之间不应该有决定性的事件依赖关系。如果您需要确定这些事件之间的顺序,您可以引入增量时间。响应时间为t+δ的事件一定比响应时间为t的事件晚,但是从模拟时间来看,还是显示了同一时间的响应。

事件队列按照时间顺序逐一响应事件队列中的事件。除了事件响应时间之外,每个事件还会标记事件类型和其他必需的参数。通过事件类型,仿真引擎可以找到相应的响应函数。其他参数用作事件响应函数的输入参数。响应函数将生成一个新事件。这些新事件也被插入到事件队列中,并根据它们的响应时间进行排序。

posedge(下降沿posedge)图3事件队列仿真框架示意图

图3显示了模拟引擎响应事件的过程。模拟引擎响应事件队列中的第一个事件e1。事件e1从队列中移除。队列从事件e2开始。模拟引擎根据e1的类型找到事件响应函数。这个响应函数调用三个模块中的事件响应函数。这些事件响应函数模拟硬件电路的行为并产生新的事件。模块1产生事件e197和e199,分别插入t1和t99模块2生成事件e198,插入t1+δ时间;模块3生成事件e200,并插入时间t100。

通过“读取第一个事件-响应事件-插入新事件”的循环,事件队列可以保持运行,直到事件队列空或者到了模拟结束的时间。另一方面,在模拟开始时,必须将初始事件插入到事件队列中,以开始模拟周期。

Verilog emulator提供了仿真引擎(在图2中的仿真器框架源文件部分),所以在编写Verilog时不必“造轮子”。但是,仿真引擎并不知道事件和响应函数之间的对应关系以及响应函数的具体功能。模拟器的工作是将Verilog文件转换成仿真响应函数,并与仿真引擎连接。生成的可执行仿真源文件和模拟器框架文件共同构成了一个完整的模拟器。

接下来分析Verilog的语法结构(进程块、赋值和延迟)是如何成为模拟器的源文件的。

过程块

总是进程块是Verilog最基本的行为级描述结构。通过在always语句中设置敏感列表,可以在适当的时候触发进程块中的操作。敏感列表中使用的条件主要是信号边沿(上升沿、下降沿)和信号值变化。如果敏感列表有多个条件,那么这些条件就是“或”,也就是说,只要满足一个条件,always过程块中的语句就会执行一次。

与模拟器相对应,always process block的语义是将一个响应函数绑定到模拟中的一个特定事件。always过程块中的语句序列是事件响应函数的函数体,always语句的敏感列表决定了这个事件响应函数绑定到哪些事件。

比如下面的D触发器。

always @(pos edge clk)begin
q & lt;= d;
end
经仿真器转换后,变成如下响应函数:

function always _ block 1:
q = d;
该响应函数将绑定到clk信号的上升沿事件。在响应clk信号的上升沿事件时,仿真器会调用函数always_block1。

一个条件可以绑定到多个事件响应函数。时钟信号等事件可以绑定到所有always模块的事件响应函数。当时钟信号的事件发生时,与之绑定的事件响应函数将被逐一调用。如果一个信号被分配到多个始终处理块中,那么一个变量将被多个事件响应函数修改。在硬件上,这些响应功能应该是并发的,没有顺序关系。然而,串行执行功能的软件无法实现这种并发性。在模拟器中,始终流程块也是有序的。Verilog规定always块之间的执行顺序是按照Verilog文件中always块的顺序。这只是为了适应软件仿真器引入的设置。

如果敏感列表中有多个条件,则意味着always块绑定到所有这些信号。如果always块不执行敏感列表或给出星号(*),这意味着always块应该绑定到procedure块中的所有右值变量。这种情况下,每个事件直接触发的事件响应函数可能会引起重复响应,即在某一时刻多次触发事件响应函数。为了避免这种错误,模拟器中引入了模拟阶段的概念。同一模拟阶段中事件的响应时间必须相同,增量时间也必须相同。在同一仿真阶段,每个事件响应信号只能触发一次。在每个仿真阶段,首先在事件队列中找到需要响应事件,然后累积需要调用的事件响应函数。最后依次调用这些事件响应函数。这确保了同一时间的信号变化只会触发同一个始终处理块一次。

除了always进程块,Verilog中还定义了其他进程块。与always进程块不同,这些进程块不是由信号的事件触发,而是将事件单独插入事件队列,并与进程块转换的响应函数绑定。初始过程块仅在模拟开始时执行一次。也就是说,如果定义了初始过程块,事件队列中的第一个事件就是初始过程块的事件。重复处理模块和永久处理模块将触发下一个响应功能的事件添加到事件响应功能末尾的电路中。该事件将在下一个delta时刻响应,以此类推。重复足够多次后,repeat过程块停止向事件队列添加事件,从而结束repeat语句。永久进程块的循环不会结束。

赋值语句

Verilog语言提供了两种赋值语句:阻塞赋值和非阻塞赋值。

a = b;//阻塞赋值
a < = b;//非阻塞赋值
根据语法定义,阻塞赋值会阻塞后续语句的执行;非阻塞赋值不会阻塞后续语句的执行。阻塞语句的效果是,在执行下一条语句之前,信号A已经被修改。非阻塞分配的效果是,只有当整个过程块被执行时,信号A的值才会被修改。注意,虽然非阻塞赋值被延迟,但赋值后的值仍然是之前获得的值。

这段话真的很费解。现在我们从软件模拟器的角度重新分析赋值语句。赋值语句实际上包括两个过程:求值和更新。评估过程确定分配给信号的值,而更新过程实际上修改信号的值。评估过程和更新过程是相互独立的。两个进程之间的关联只是需要赋值的值。

阻塞分配的评估过程和更新过程被连续执行,并且在评估之后被立即更新。因此,当执行下一条语句时,信号已经被修改。当转换为仿真程序代码时,阻塞赋值不需要特殊处理。例如

always @(a,b,c)begin:add _ mux 1
t = a+b;
d = t * c;
end
以上代码转换后的事件响应函数为

函数add _ mux 1:
t = a+b;
d = t * c;
非阻塞赋值的求值过程和更新过程是分开的。在块中执行赋值语句时,只执行求值过程来确定需要赋给信号的值,然后继续向后执行。更新过程被延迟,直到整个过程块被执行。例如

always @(a,b,c)begin:add _ mu x2
t & lt;= a+b;
d &lt。= t * c;
end
以上代码转换后的事件响应函数为

函数add _ mu x2:
t _ update = a+b;
d _ update = t * c;
t = t _ update;
d = d _ update;
当阻塞赋值和非阻塞赋值混合使用时,遵循相同的规则。例如

always @(a,b,c)begin:add _ mux 3
t & lt;= a+b;
d = t * c;
end
以上代码转换后的事件响应函数为

函数add _ mux 3:
t _ update = a+b;
d = t * c;
t = t _ update;
信号的分配会产生一个事件,表示分配的信号已经改变。如果其他过程块依赖于所分配的信号,该事件将被添加到事件队列中;否则,该事件将被忽略。事件的响应时间是当前时间加上δ。赋值语句是仿真引擎持续运行的关键。大多数always块通过赋值语句向事件队列添加新事件。

延迟行为

Verilog语言的延时语句虽然不能合成,但在仿真过程中被广泛使用。延迟语句可以用来在testbench中构建时钟信号和激励,也可以用来在Verilog模块中模拟实际电路的延迟。Delay语句可以出现在两条赋值语句之间,也可以出现在一条赋值语句的中间。

# 3 a = b;//Delay语句在赋值语句之间
a = # 3 b;//延迟语句在赋值语句内部
赋值语句之间的延迟语句可以延迟语句的执行。对于模拟器,赋值语句之间的延迟语句有两个功能。首先,delay语句将暂停当前always块的执行,结束当前模拟阶段,更新之前未完成的赋值,完成当前事件的响应并将控制返回到事件队列。然后,delay语句会向事件队列添加一个新事件。此事件表示函数的其余部分将在delay语句指定的时间执行。例如

always @(a,b,c)begin:add _ mux 4
t & lt;= a+b;
# 1d = t * c;
end
以上代码转换后的事件响应函数为

函数add _ mu x4 _ 1:
t _ update = a+b;
t = t _ update;
addEvent( curr_time + 1,add _ mu x4 _ 2);

function add _ mu x4 _ 2:
d = t * c;
verilog文件中的一个程序块被转换成两个函数。第一个函数add_mux4_1对应于delay语句之前的部分,第二个函数add_mux4_2对应于delay语句之后的部分。

Add_event是本文中定义的一个原语,这意味着向事件队列中添加一个事件。第一个参数表示事件响应的时间,第二个参数表示响应事件需要调用的事件响应函数。从第三个参数开始,以下参数将作为事件响应函数的参数传递给事件响应函数。

Add _ event (curr_time+1,add _ mux4 _ 2)表示在当前时间(curr_time)后一个时间单位响应此事件。事件需要调用add_mux4_2函数。响应函数不需要额外的参数。在调用add_mux4_2时,信号t已经被更新。

赋值语句中间的delay语句将求值和更新阶段分为两个时刻。执行该语句时,评估过程仍会执行,但更新过程会延迟到delay语句指定的时间。delay语句是否阻塞process块的执行取决于赋值语句本身。如果是阻塞赋值语句,赋值语句中间的delay语句会阻塞进程块的执行;如果赋值是非阻塞的,delay语句不会阻塞过程块的执行。例如

always @(a,b,c)begin:add _ mux 5
t & lt;= # 1 a+b;
d = # 2t * c;
end
以上代码转换后的事件响应函数为

函数add _ mux 5 _ 1:
t _ update = a+b;//1
d _ update = t * c;// 2
addEvent( curr_time + 1,update_t,t _ update);
addEvent( curr_time + 2,add_mux5_2,d _ update);

函数update _ t(t _ update):
t = t _ update;//3

函数add _ mux 5 _ 2(d _ update):
d = d _ update;// 4
如果没有delay语句,事件响应函数的执行顺序应该是1->;2->;4->;3。由于第一条语句中的delay语句,语句3需要在当前时间后一个时间单位执行,即update _ t,T_update作为事件响应函数的参数,更新为update_t中的信号T..由于第二条语句中的delay语句,进程块被中断成两部分,第二个函数需要在当前时间后2个时间单位执行,即add_mux5_2。Add_mux5_2需要使用d_update作为参数。

理解了这一层,就可以处理更复杂的波形了。例如,下面的代码。

模块测试;
reg x,y,z;
assign # 25a = 1;
总是从
# 20;
x = # 10 a;
# 3y = a;
# 3 z = a;
# 7;
end
endmodule
模拟器转换后,上面的Verilog语句会形成下面的事件响应函数。

函数赋值1:
a = 1;

函数always 1 _ 1:
add event(curr _ time+20,always 1 _ 2);

函数always 1 _ 2:
x _ update = a;
addEvent( curr_time + 10,always1_3,x _ update);

函数always 1 _ 3(x _ update):
x = x _ update;
addEvent( curr_time + 3,always 1 _ 4);

函数always 1 _ 4:
y = a;
addEvent( curr_time + 3,always 1 _ 5);

函数always 1 _ 5:
z = a;
addEvent( curr_time + 7,always 1 _ 6);

函数always 1 _ 6:
add event(curr _ time+delta,always 1 _ 1);
在模拟开始时,首先在事件队列中添加两个事件,即0+25处的call assign1和0+0处的call always1_1。响应过程如图4所示。Always进程块被delay语句分成六个响应函数。每个部分都向事件队列添加一个事件,该事件可以触发下一个响应函数。信号X的第一次评估发生在20: 00,而第一次更新发生在30: 00,因此信号X的第一次赋值仍然是X..直到第二次评估(时间63)才能获得有效信号1,并且它在时间73被更新为信号X。

posedge(下降沿posedge)需要注意的是,虽然本文提供了一种更容易理解行为级描述的执行过程的思路,但仍然不建议您在process块中混合使用阻塞赋值和非阻塞赋值。混合使用赋值语句是很危险的。

分配任务

前面的介绍主要关注流程块。对于Assign赋值语句,原理实际上是一样的。例如

分配a = # 5b & amp;c;
这个赋值语句也可以看作是一个事件响应函数。这个函数绑定的事件是信号B或信号C发生变化。延迟语句的效果是一样的。Delay语句将评估和更新过程分开。评估信号B或信号C何时改变,并向事件队列添加新的更新事件。5个时间单位后,评估值更新为信号A,以响应更新事件。

转换后的事件响应函数如下。

函数分配1:
a _ update = b & amp;c;
addEvent( curr_time + 5,update_a,a _ update);

函数update _ a(a _ update):
a = a _ update;
调试

Verilog仿真器一般提供Verilog代码的调试能力,如断点、单步运行等。调试模式可以在VCS、ModelSim、Vivado和Quartus中找到。断点和单步操作是典型的软件调试方法,是软件工程师的看家本领。但是对于硬件来说,断点和单步操作是无法理解的,因为硬件是并行的。如果把断点理解为硬件电路在某一时刻的状态,那么就应该同时中断几条语句。硬件不会像软件一样在某个功能中被中断而单步执行,其他进程块或语句没有影响。

如前所述,Verilog不是一种可执行语言。真正可执行的仿真程序由仿真器提供的仿真框架源代码和Verilog语言转换的仿真程序源代码组成。实际上,Verilog语言调试的断点并不是添加到硬件或Verilog源文件中,而是添加到可执行仿真程序中相应的事件响应函数中。单步调试对象也是可执行模拟程序中的事件响应函数。因此,Verilog代码可以引入断点和单步调试。

在进一步解释Verilog调试器的机制之前,有必要解释一下软件调试器是如何调试程序的。为了使可执行文件可调试,编译器将向可执行程序添加调试信息。以C语言为例,编译器会在可执行文件中添加调试信息(如图5)。添加的位置在对应于C语言语句的汇编代码段的开头。在软件调试过程中,软件调试器会在有调试信息的地方暂停(如0x4005a5)。单步调试时,每一步都在C语言语句的开头停止(如0x4005bf)。

posedge(下降沿posedge)图5添加到可调试程序中的调试信息(通过使用objdump命令获得)

Verilog调试器的作用是将可执行仿真器与Verilog语言相匹配。一种思路是将编译器插入的调试信息与Verilog语言对应起来。编译器插入的调试信息对应于仿真器生成的可执行仿真源文件。因此,Verilog调试器可以获得调试信息和Verilog语句之间的对应关系。另一种思路是通过编译器直接将Verilog语言对应的调试信息添加到可执行的仿真程序中。这样,Verilog调试器就可以从可执行程序中获取必要的信息,而不需要额外的信息源。

Verilog调试器只能在能够对应Verilog语言的代码部分添加断点,也就是只能在事件响应函数中添加断点。Verilog调试器无法调试仿真器提供的仿真框架源代码。当分步调试遇到always block或assign语句结束时,调试器不会进入模拟引擎,而是直接跳转到下一个事件响应函数。此外,Verilog调试器还将断点和单步调试的粒度限制在Verilog语句,但不能进一步将粒度降低到可执行仿真程序的语句甚至汇编级。

posedge(下降沿posedge)图6向Verilog程序添加断点的过程

以图6中的Verilog程序为例,通过Verilog仿真器得到右图所示的仿真源文件,然后编译得到可执行程序。在左边Verilog程序的一行(图5中左边第七行)设置断点,实际上是在右边可执行程序的事件响应函数always1_4中设置的(图5中右边第二行)。每当仿真器运行到always1_4时,它都会触发一个中断并挂起程序执行。通过仿真器添加的调试提示信息,调试器可以知道中断的位置是Verilog语言的第7行,然后可以显示在图形界面上。

从断点位置开始单步调试。从可执行仿真程序的层次来说,应该是在第3行暂停,执行后会处于第3行的状态。但是Verilog调试器会对仿真框架的程序进行过滤,即过滤掉不能对应Verilog程序的语句。因此,可执行程序不会在第3行后暂停,而是继续执行。第3行结束后,程序从事件响应函数返回,进入模拟框架。模拟器框架的代码也会被调试器忽略,直到模拟器进入下一个事件响应函数。最后,程序转到always1_5。调试器会在第6行暂停,中断的位置对应Verilog软件的第8行。正在运行的程序的标志将显示在图6中第8行的位置。

以上过程用户都是看不到的。从用户的角度,我们只能看到程序指针从第7行跳到第8行,第7行语句的效果显示在波形图上。这就是Verilog语言调试背后隐藏的过程,其核心仍然是软件调试。

标签

本文的初衷是提供一种通过仿真器理解Verilog语言的思路。本文对Verilog模拟器的描述采用了最简单直接的思路,当然也是效率最低的。实际模拟器会通过各种软件技巧进行优化,提高模拟效率。本文使用的一些概念借用了SystemC,如仿真阶段和“评估-更新”机制。电路模拟器的设计思路和概念是相似或相通的,可以举一反三。

如果读者想了解更多关于Verilog emulator的知识,可以看看开源Verilog emulator iVerilog的源代码。此外,SystemC也是一个很好的硬件电路仿真框架。建议学习SystemC标准。IEEE SystemC标准将解释SystemC所需的模拟引擎和编程规范。

作者才华横溢,什么都错过了。请批评指正。

作者简介

作者:王君实

电子科技大学博士。主要研究方向为片上互连结构和计算机体系结构。在IEEE Transactions on Computers(CCF A期刊)等高水平期刊和CODES+ISSS、ISCAS等顶级会议上发表高水平论文10余篇,申请专利4项。长期从事片上系统建模和模型开发。在此期间,他领导了面向多核SOC的高级建模和设计工具ESYSim的开发。该系列工具获得了中国研究生电子设计大赛(即本届中国研究生“创芯”大赛的前身)集成电路大赛特等奖。现在从事CPU性能建模和优化工作。

*声明:本文由作者原创。文章内容为作者个人观点。《半导体工业观察》的转载只是为了传达一种不同的观点,并不代表《半导体工业观察》赞同或支持这种观点。如有异议,请联系半导体产业观察。

今天是半导体产业观察为你分享的第2113期内容。请注意。

半导体行业观察

“半导体第一垂直媒体”

实时专业原创深度

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。

作者:美站资讯,如若转载,请注明出处:https://www.meizw.com/n/32941.html

发表回复

登录后才能评论