3.1 进程敏感表与信号赋值


3.1 进程敏感表与信号赋值

本节摘要:进程是 VHDL 并发世界的核心引擎,敏感表决定它何时被唤醒,信号赋值的 delta 周期决定新值何时可见。本节讲透事件驱动仿真的运转机制,给出敏感表配平法则,并用两个对照实验固化"信号赋值非即时生效"的认知。

从一个问题开始:进程到底什么时候跑

把第 1 章那个 LED 设计里的进程单独拎出来看:

process (clk) begin if rising_edge(clk) then if en = '1' then reg <= not reg; end if; end if; end process;

括号里的 (clk) 是敏感表。仿真器的规则只有一条:敏感表里任何一个信号的值发生变化,进程就从第一条语句执行到最后一条,然后休眠。clk 每 10 ns 翻转一次,进程每 10 ns 醒一次;醒着的时间里顺序语句依次执行,信号赋值把"新值请求"挂到事件队列里。整个仿真就是无数进程被事件唤醒、又制造新事件、再唤醒别的进程的连锁反应——这就是"事件驱动仿真"。

敏感表的配平有一条硬法则:进程里读过的所有信号,都必须出现在敏感表里。上面读的是 clk 和 en,敏感表只写了 clk——这是故意留的破绽。仿真时会发生什么?en 变化不会唤醒进程,但进程本来就只在时钟沿干活,en 只要赶在时钟沿之前稳定,行为照样正确。这就是时序进程只挂 clk 也能仿对的原理:它对 en 的采样发生在被 clk 唤醒之后。但组合进程少写敏感信号就是灾难,看实验二。

实验一:delta 周期,看不见的一瞬

信号赋值 y <= a and b; 生效的时刻不是"现在",而是"现在加一个 delta"。delta 不是纳秒,它是仿真器排序用的无穷小时间片。串三个赋值做个实验:

architecture delta_demo of evt is signal a, n1, n2, n3 : std_logic := '0'; begin a <= '1' after 10 ns; n1 <= not a; -- a 变后第 1 个 delta 更新 n2 <= not n1; -- 第 2 个 delta n3 <= not n2; -- 第 3 个 delta end architecture delta_demo;

在 10 ns 处放大波形:a 变为 '1' 的同一时刻,n1、n2、n3 依次在第一、二、三个 delta 里翻转——仿真时间都停在 10 ns,肉眼在波形上分辨不出先后,但仿真器严格按此排序。物理含义:每级逻辑有传播延迟,delta 是它的零成本化身。跨进程互联时这个"一 delta 一级"的特性会累积,理解它,你才能解释"为什么两个进程互相赋值不会死锁"。

图:事件驱动与 delta 周期推进

图:事件驱动与 delta 周期推进

实验二:组合进程漏写敏感信号,仿真与综合分家

组合逻辑写成进程时,敏感表漏信号会出现"仿真对、上板错"的经典事故:

-- 有隐患的写法:敏感表漏了 b process (a) begin if sel = '1' then y <= a; else y <= b; end if; end process; -- 正确写法:读到谁就挂谁 process (a, b, sel) begin if sel = '1' then y <= a; else y <= b; end if; end process;

隐患版本里,b 单独变化时进程不醒,y 保持旧值——仿真波形看起来"偶尔卡住",而综合器根本不看敏感表(它按语句结构生成选择器),做出来的电路完全正常。于是出现最阴险的局面:仿真有 bug、真电路没 bug,你浪费一下午追一个不存在的硬件故障。VHDL-2008 给了终极解法:process (all),敏感表自动配平。新代码一律用 all,老代码审查时重点盯敏感表。

⚠️ 常见坑:时序进程敏感表混入复位信号,写成 process (clk, rst) 又用 if rst='1' 电平检测,会综合出锁存器式的怪电路。同步复位就只挂 clk;要写异步复位,用 if rising_edge(clk) or falling_edge(rst) 的标准句式,第 5 章展开。

💡 关键直觉:进程是"一小片电路的行为描述",敏感表是这片电路的输入清单。清单写漏,等于电路少接了一根输入线——仿真器忠实反映漏线,综合器自作主张补线,两边就分家了。

本节要点回顾

  • 唤醒条件:敏感表中任一信号值变化,进程整体执行一遍后休眠;
  • 配平法则:进程内读过的信号全进敏感表,或直接用 process(all);
  • delta 是排序时间片:同一时刻内新值按 delta 逐级可见,波形上不可分辨;
  • 组合进程漏敏感信号:仿真卡旧值、综合却正常——审查老代码的高发雷区;
  • 时序进程只挂时钟:复位风格与敏感表必须配套,混搭出怪电路。

实战延伸:敏感表与时钟边沿的两种写法

写法 1:显式列出所有敏感信号

process(clock, reset, data_in) begin if reset = '1' then ... elsif rising_edge(clock) then ... end if; end process;

写法 2:VHDL-2008 引入的 process(all) 隐式敏感

process(all) begin if reset = '1' then ... elsif rising_edge(clock) then ... end if; end process;

写法 2 简洁但容易出错——任何被读的信号改变都会触发进程,可能引入不必要的逻辑翻转。工程上推荐写法 1(显式敏感),更容易 review。

仿真器提示

ModelSim 和 GHDL 在敏感表缺失时会发出警告("signal X is not in sensitivity list")。这种警告应该视为错误处理——它意味着仿真行为与综合后的硬件行为不一致(综合工具会自动补敏感表,但仿真器严格按 VHDL 语义)。

实战延伸:敏感表动态推断的工具

VHDL-2008 引入了 process(all) 关键字,自动推断敏感表。但实际项目里推荐显式列出:

process(clock, reset, data_in, valid) begin -- 显式列出所有被读信号 end process;

GHDL 和 ModelSim 都提供 lint 工具(ghdl -s、modelsim -lint),会报告"signal X is not in sensitivity list"。在 CI 流水线里加入 lint 步骤,能在仿真前捕获此类 bug。

反例

依赖仿真器自动补敏感表。GHDL 严格按 VHDL 语义,缺失敏感表时不会更新信号。ModelSim 在某些模式下会警告但不报错。综合工具(Vivado/Quartus)会自动补敏感表,但综合结果可能与仿真行为不一致——这种"仿真过、综合错"的 bug 最难调。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U