本节摘要:架构语句区的并发语句本质都是进程的语法糖,信号赋值与变量赋值相差一个 delta 周期的生效时机。本节用"交换两个数"的对照实验把这一拍之差钉死,并整理条件赋值、选择赋值与进程的互改写关系。
下面这段代码想实现"同步交换两个寄存器的值",用信号写:
architecture wrong of swap is signal x, y : unsigned(7 downto 0) := (others => '0'); begin process (clk) begin if rising_edge(clk) then x <= y; -- 请求:下个 delta 把 y 的旧值给 x y <= x; -- 请求:下个 delta 把 x 的旧值给 y end if; end process; end architecture wrong;
它不但没错,而且是完全正确的交换电路!两个信号赋值都读的是"本周期开始时的旧值",delta 结算时同时换值——这正是硬件里两根线交叉对接的行为。但把其中一处换成 C 语言思维的"先存临时变量再交换",或者把 x 换成变量,行为立刻不同。理解差异之前,先把并发语句的家底盘一遍。
架构语句区能写的并发语句,每一种都能改写成进程。对照表如下:
| 并发写法 | 等价进程写法 | 用途倾向 |
|---|---|---|
| y <= a and b; | process(a,b) begin y <= a and b; end process | 一句话逻辑 |
| y <= a when s='1' else b; | process(a,b,s) begin if s='1' then y<=a; else y<=b; end if; end process | 二选一带默认 |
| with s select y <= a when "00", b when others; | process(s,a,b) begin case s is ... end case; end process | 多分支并列 |
| 实例化语句 | 不可改写 | 模块互联 |
改写关系说明:when-else 是 if 的糖,with-select 是 case 的糖。写一行的逻辑用糖,逻辑复杂到需要中间变量时退回进程——判断标准不是行数,是"是否需要中间状态"。另外注意并发赋值的多驱动问题:同一目标信号在架构里被两条并发赋值各写一次,编译能过,仿真出 'X'——第 1 章见过,这里是机制层面的完整解释:两份驱动器打架。
两者的差异浓缩成一张表:
| 对比项 | 信号 signal | 变量 variable |
|---|---|---|
| 声明位置 | 架构声明区或进程外 | 进程声明区 |
| 赋值符号 | <= | := |
| 生效时机 | delta 结算后(下一瞬间) | 立即 |
| 作用范围 | 全架构可见 | 仅本进程 |
| 物理对应 | 连线或寄存器 | 无直接对应,综合后融入逻辑 |
"立即生效"与"下一瞬间生效"的差异,用计数实验最能看清。需求:时钟沿上对计数器先加二,再判断是否超过上限,超过就归零。用信号写:
process (clk) begin if rising_edge(clk) then cnt <= cnt + 2; -- 请求加二 if cnt > 250 then cnt <= (others=>'0'); -- 判断的是旧值! end if; end if; end process;
判断用的 cnt 是加二之前的旧值,边界检查慢一拍。用变量改写:
process (clk) variable v : unsigned(7 downto 0); begin if rising_edge(clk) then v := cnt + 2; -- 立即生效 if v > 252 then v := (others=>'0'); -- 判断的是新值 end if; cnt <= v; -- 结算回信号 end if; end process;
变量版本在进程内实时反映运算结果,边界判断精确。这就是变量的正确用法:进程内的便签纸,算完把最终结果抄给信号。工程纪律也由此而来:变量永远在进程末尾把值交给某个信号,别让重要状态只活在变量里——否则别的进程看不到它。

把 wrong 架构配上测试台跑仿真:初始化 x 为三、y 为七,给一个时钟沿,波形里 x 变七、y 变三——同步交换成功。再把其中一个信号改成变量试试,行为立刻坏掉:变量读不到"对面"的值,因为变量只在进程内存在,另一根线的旧值得靠信号传递。这个实验做完,"信号是进程间通信的唯一通道、变量是进程内便签"这两句话就长在直觉里了。
⚠️ 常见坑:变量在进程里"记住"了上次的值,于是有人用变量存状态想做寄存器——可以,但只有"进程末尾被读且跨周期存活"的变量才会综合成寄存器,规则绕脑。清晰起见:状态一律用信号存,变量只做进程内中间计算。
💡 关键直觉:并发语句是"看电路"的视角,进程是"看行为"的视角,两者互相可译。而信号与变量的选择,本质是在选择"要不要让下一行代码立刻看到结果"。
VHDL 信号赋值不是立即生效的,而是到 wait / 进程挂起时才更新。这是 VHDL 最反直觉的地方:
process(clock) begin if rising_edge(clock) then a <= b; -- a 立即"应该"等于 b c <= a; -- 但 c 拿到的是 a 的旧值,不是 a <= b 之后的值 end if; end process;
正确写法(用变量作中间缓冲):
process(clock) variable tmp : std_logic; begin if rising_edge(clock) then tmp := b; -- 变量立即生效 a <= tmp; -- 信号在进程末更新 c <= tmp; -- a 和 c 都拿到新值 end if; end process;
在时钟进程里需要"先读再写"的链式赋值时,用变量作缓冲。在组合进程里避免链式信号赋值(容易产生 delta cycle 警告,且综合结果不可预期)。