Verilog仿真原理

1. 电路与仿真

1.1 电路是并行的

1.2 仿真时串行的

EDA工具是软件,在CPU中串行执行,所以仿真是串行的,也是顺序执行。

Verilog的并行性在仿真中是通过其语义(semantics)来实现的。语义就是Verilog的语言含义。比如@是等待事件,#10是延迟10个时间单位,块语句都是并行的。

2. Verilog仿真概念

2.1 仿真时间

仿真事件是仿真器维护的时间值,它用来模拟电路真实的运行事件。

Verilog仿真严格按照仿真时间 的时间轴向前推进。同一时刻有多个事件,那么按照优先级来执行,也就是后面要将的分层事件队列

2.2 事件驱动

事件分为计算事件和更新事件。

线网和寄存器值发生变化,叫做更新事件。一个更新事件执行后,所有对这个事件敏感的进程都被激活,以随机顺序计算。进程的计算是计算事件。

更新事件会产生计算事件,计算事件也会产生更新事件。相互触发,推进仿真进行。

2.3 进程

进程包括initial、always、module、原语、assign、异步任务和过程赋值等。

进程可以被激活和挂起。仿真器总是在处理被激活的一个进程,而挂起其他所有的进程。

多个进程在同一仿真时间执行时,仿真器会对它们的顺序进行调度。只有当前时刻所有的进程都执行完进入挂起状态,仿真时间才会向前推进。

进程的挂起有 事件语句@、延迟#、等待语句wait。当事件到来、延迟已过、wait的表达式为真,则进程激活

2.4 调度

调度就是安排同一时刻中事件的执行顺序,参见下面的分层事件队列

2.5 时序控制

时序控制有事件语句@、延迟#、等待语句wait。当事件到来、延迟已过、wait的表达式为真,则进程激活

2.6 进程、事件和仿真时间的关系

​ 事件在不同的时间发生,为了跟踪事件,以正确的时间处理事件,将事件放在事件队列中,由仿真时间来负责排序。特定时刻的所有事件放在一起,调度器可以将事件插入到事件队列中来把事件调度到将来某个时刻,也可以把事件弹出——执行事件。

2.7 verilog语言的不确定性

  1. 零时刻时,进程的执行顺序任意
  2. 各个进程的执行顺序不确定

3. 分层事件队列

3.1 事件队列

内容 描述
active 阻塞赋值;连续赋值;非阻塞赋值的右值;$display; 当前时间 Active events occur at the current simulation time and can be processed in any order.
inactive 显式0延迟阻塞赋值; 当前时间 Inactive events occur at the current simulation time, but shall be processed after all the active events
are processed
NBA 非阻塞赋值更新事件 当前时间 Nonblocking assign update events have been evaluated during some previous simulation time, but
shall be assigned at this simulation time after all the active and inactive events are processed.
monitor $monitor,$strobe函数 当前时间 Monitor events shall be processed after all the active, inactive, and nonblocking assign update
events are processed.
将来仿真事件 被调度到将来时间的事件 将来时间 Future events occur at some future simulation time. Future events are divided into future inactive
events and future nonblocking assignment update events.

3.2 仿真参考模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
while (there are events) {
if (no active events) {
if (there are inactive events) {
activate all inactive events;
} else if (there are nonblocking assign update events) {
activate all nonblocking assign update events;
} else if (there are monitor events) {
activate all monitor events;
} else {
advance T to the next event time;
activate all inactive events for time T;
}
}
E = any active event;
if (E is an update event) {
update the modified object;
add evaluation events for sensitive processes to event queue;
} else { /* shall be an evaluation event */
evaluate the process;
add update events to the event queue;
}
}

当前时间内所有的active event被称为仿真周期,

0延迟的阻塞赋值 assign #0 a = b; 阻塞赋值的计算在active区域执行,但他的更新时间因为有0延迟,所以要在下一个仿真周期处理,也就是inactive区域被激活,此时对激活后的inactive event的处理也是一个仿真周期。

1
2
3
4
always@(posedge clk) begin
sum1 <= a+b; //先执行a+b的计算事件,更新事件调度到当前仿真时刻的NBA区域
sum2 <= #5 a+b; //先执行a+b的计算事件,更新事件调度到5ns后仿真时间的NBA区域
end

4. 仿真延迟

4.1 惯性延迟

器件上的延迟,比如反相器的延迟。

对惯性延迟的模拟:

1
assign #5 a = ~b;

b变化后,a的计算事件执行,但是更新事件要到5ns以后。这模拟了一个反相器的延迟。

4.2 传导延迟

连线上的延迟。

5. 阻塞和非阻塞

没什么好补充的,通俗易懂。

在仿真时,非阻塞赋值先进行采样——右值进行计算。然后才执行阻塞赋值。

1
2
3
4
5
6
7
8
always@(posedge clk) begin
data <= a;
end
assign a = b + c;
/*
如果在时钟上升沿处,恰好a的值从0到1变化,那么因为先进行非阻塞采样,在always快中,
右值a采样到的还是0,然后再执行阻塞赋值a变成1,但这时always块中的a还是0,它已经采样过了。
*/

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!