type
status
date
slug
summary
tags
category
icon
password
🤔
本文将讨论一个很容易困惑 Verilog 新手的问题:行为级仿真时,时钟跳变沿瞬间数据发生变化,采样左侧值还是右侧值?

📝 问题导入

以下列模块为例子,该模块实现奇偶校验的功能,信号in 每出现一次高电平,odd 翻转一次:
testbench 关键逻辑如下:
使用 iverilog 工具仿真波形如下:
notion image
此处可以发现inodd是同步变化的 ,in输入高电平,odd立即变化。但是模块中odd 的赋值是时序逻辑,时钟上升沿采样的应该是in 前一个周期的值,问题出在哪里了呢?
⚠️
RTL 行为级仿真无法模拟物理上的延迟情况,因此波形上看上去像是数据紧贴时钟边沿变化,实际上并不满足建立时间与保持时间。一种物理上理想的情况是:数据跳变发生在时钟边沿前后间隔的 无穷小时间内。
notion image

🤗 阻塞/非阻塞赋值

A blocking procedural assignment statement shall be executed before the execution of the statements that follow it in a sequential block (see 9.8.1). A blocking procedural assignment statement shall not prevent the execution of statements that follow it in a parallel block (see 9.8.2)
The nonblocking procedural assignment allows assignment scheduling without blocking the procedural flow. The nonblocking procedural assignment statement can be used whenever several variable assignments within the same time step can be made without regard to order or dependence upon each other.
—— 引用自 IEEE Standard for Verilog®Hardware Description Language
总结一下:
  • “=” 在顺序块会先完成自身的赋值,后续语句依赖它的赋值。在 RTL 仿真中等效为顺序执行,而在实际电路中,个人认为这种说法容易造成误解。尽管称为“阻塞”,但实际电路仍然将这种描述实现为并行的逻辑,并非后级信号需要等待前级信号计算完毕,而是后级信号的有效输出“依赖”前级信号。例如加法器的级联形成的进位链,若考虑门级传输延迟,则最终的进位输出在平均最大延迟时间之前仍然是不确定的。而在并行块(fork join),阻塞过程赋值语句不会阻止其后续语句的执行。
  • "<=" 为非阻塞赋值,在并行块中使用时不会阻塞程序流程。非阻塞赋值允许在不考虑顺序或相互依赖的情况下进行变量赋值调度。这类赋值语句被广泛用于时序逻辑设计中,保证信号的变化发生在下一个时间步,从而更好地模拟实际硬件行为。

🍀 time-slot 和 delta-cycle

  • time-slot(时间片):`timescale 定义了仿真时间单位和精度,将连续时间离散化。time-slot 是仿真器事件调度的基本单位,对应一个具体的时间节点。众所周知,硬件电路是全并行的,而仿真器作为软件只能通过串行执行指令模拟硬件行为,由此诞生了仿真器的事件调度模型。每一个时间片内有一系列按照一定顺序执行的关键区域(Region),在每一个时间片的出口将完成所有事件的更新。
  • delta-cycle(零延迟时间单位):它是一个比仿真精度更小的时间单位,通常无法在仿真波形中直接显示。在同一个 time-slot 内可以有无穷个 delta-cycle,delta-cycle 会跟随事件迭代而增加。例如在时间节点 100 ns,有 ABC 三个信号需要驱动,它们所处的有序关键区域的执行顺序为为 A → B → C,并且 ABC 各自的更新将不会再触发其他任何敏感事件,则 ABC 完成更新的时间节点为 100ns + 0,100ns + 1,100 ns + 2。
更详细的关于事件调度可参考:
notion image

🔍 仿真分析

已知阻塞赋值位于 Active 区域,非阻塞赋值位于 NBA 区域,后者理应比前者的 delta-cycle 偏移量更大。若此时有另一个信号依赖与前一个非阻塞赋值的更新,那么它的 delta-cycle 将再次增加。对于先前的 testbench:
我们可以推测,以clk信号时间节点为基准,in首先完成更新,然后对odd进行更新,此时条件满足,odd完成一次翻转。又由于 delta-cycle 的不可见性,在波形上表现出inodd同步变化的现象。这与 iverilog 的仿真波形相符。
因此,改为in <= seq[i]; 则将增加一个 delta-cycle,使得时钟采样的实际是in更新前的旧值,此时条件不满足,odd要直到下一个时钟上升沿才会更新。
💡
这样的调度非常符合真实硬件电路。对于组合逻辑,理想情况下是立即响应电路的变化,以至于上述的行为看起来像是在时钟沿到来前一刻就已经完成了更新。而时序逻辑通常考虑到寄存器 D 到 Q 有比组合逻辑更大的延迟,因此上述行为看起来像是在时钟沿到来之后才更新。对于一个同步信号,后者更符合现实情况。

🔍 行为级仿真验证

Modelsim仿真结果:
时间轴置于时钟上升沿,点击上方工具栏 Expanded Time Deltas Mode → Expand Time At Active Cursor。这样可以清晰查看 delta-cycle 的情况,实际开发中无需刻意关心仿真器复杂的底层事件调度。
  1. “=” 赋值。
notion image
2. “ <=” 赋值。
notion image
💡
对代码进行如下修正也可以产生 delta-cycle:
 

💡 综合后仿真

综合后仿真分为功能性和时序性仿真,前者只是将综合后生成的网表信号添加至仿真中,后者继续引入了软件时序评估,更贴近真实情况。同时,我们可以看到,在引入物理延迟后,两种写法在波形上实际没有差别。
注意,综合后仿真引入会默认添加 glbl.v 文件,内含一个持续 100 ns 的全局复位信号 GSR,建议将有效输入信号延迟相应时间输入。参考 AMD 官方手册:
notion image
  1. “=” 赋值。
notion image
  1. “<=” 赋值。
notion image

🔗 参考文章

使用Digital-IDE与Xilinx工具链联合开发Linux 环境下 Vivado 与 Modelsim 联合仿真无法自动弹出界面解决方案
Loading...