6.1 连续赋值语句(Continuous Assignment)
一、什么是连续赋值?
连续赋值(Continuous Assignment) 是数据流建模的基础语句,用于驱动线网(wire)类型的变量。
核心理解: 连续赋值语句始终处于激活状态。只要右边表达式中的任何一个操作数发生变化,表达式就会被重新计算,结果立即(或在指定延迟后)赋给左边的线网变量。
语法:
assign #延迟 线网名 = 表达式;
示例:
wire out;
assign out = in1 & in2; // out 始终等于 in1 AND in2
二、连续赋值的特点
| 特点 | 说明 |
|---|---|
| 始终激活 | 只要操作数变化,立即重新计算 |
| 左值必须是线网 | 赋值目标必须是 wire 类型,不能是 reg |
| 右值可以是任何类型 | 可以是 wire、reg、常数、函数调用等 |
| 延迟可选 | 可以指定赋值延迟,模拟电路传输延迟 |
// 多个连续赋值语句
wire out1, out2;
reg [15:0] reg_a, reg_b;
assign out1 = in1 ^ in2; // 异或
assign out2 = (a & b) | (c & d); // 复杂组合逻辑
assign addr[15:0] = addr1[15:0] ^ addr2[15:0]; // 向量运算
连续赋值 vs 过程赋值:
-
连续赋值(
assign):始终激活,操作数变化时自动更新 → 用于组合逻辑 -
过程赋值(在
always/initial中):只有执行到该语句时才赋值 → 用于时序逻辑
三、隐式连续赋值
在声明 wire 的同时直接赋值:
// 方法1:先声明再赋值
wire out;
assign out = in1 & in2;
// 方法2:隐式连续赋值(声明时直接赋值)------ 更简洁!
wire out = in1 & in2;
四、隐式线网声明
如果信号名出现在连续赋值语句的左侧 ,Verilog 会隐式声明该信号为线网类型。
// 没有提前声明 out,直接使用
assign out = in1 & in2; // out 被隐式声明为 wire
隐式线网声明可能带来风险 :如果拼写错误(比如把 out 写成 outt),编译器会隐式声明一个新的线网,而不会报错。建议在代码中显式声明所有线网,或者开启编译器警告。
6.2 延迟(Delays)
连续赋值语句中的延迟用于控制操作数变化到输出更新的时间间隔。
一、三种延迟指定方式
// 1. 普通赋值延迟
assign #10 out = in1 & in2;
// in1或in2变化后,等待10个时间单位,out才更新
// 2. 隐式连续赋值延迟(声明时指定)
wire #10 out = in1 & in2;
// 等价于上面的写法
// 3. 线网声明延迟
wire #10 out;
assign out = in1 & in2;
// 任何对out的赋值都会延迟10个时间单位
二、惯性延迟(Inertial Delay)
惯性延迟 是Verilog连续赋值的重要特性:输入脉冲宽度小于赋值延迟时,输出不会发生变化。
in1 ──┬────┬──
│ │
in2 ──┴────┴──
│ │
───┘ └───
out ─────────────
│ │
│ │ ← 脉冲宽度 < 10,被过滤掉
└────┘
assign #10 out = in1 & in2;
| 时间 | in1变化 | in2变化 | out变化 |
|---|---|---|---|
| 20ns | 0→1 | 0→1 | 30ns时 0→1 |
| 60ns | 1→0 | 1→0 | 70ns时 1→0 |
| 80ns | 0→1 | - | 90ns时计算,此时in1已变回0 → out保持0 |
💡 脉冲过滤:如果输入脉冲宽度小于延迟值(如80ns-85ns的脉冲宽度5 < 延迟10),输出不会响应。这就是惯性延迟的"滤波"效果。
6.3 表达式、操作符和操作数
一、基本概念
| 术语 | 定义 | 示例 |
|---|---|---|
| 表达式 | 由操作符和操作数组成的运算式 | a & b、addr1 + addr2 |
| 操作数 | 参与运算的对象 | 常数、线网、寄存器、位选、函数调用 |
| 操作符 | 规定运算类型 | &、+、<<、?: |
// 表达式示例
c = a & b; // a和b是操作数,&是操作符
sum = addr1 + addr2; // addr1和addr2是操作数
out = sel ? in1 : in0; // 三目操作符
6.4 操作符类型详解
一、算术操作符(Arithmetic Operators)
| 操作符 | 功能 | 示例 |
|---|---|---|
* |
乘法 | a * b |
/ |
除法 | a / b |
+ |
加法 | a + b |
- |
减法 | a - b |
% |
取模(求余数) | a % b |
** |
求幂 | a ** b |
reg [3:0] A, B, sum;
reg [7:0] product;
A = 4'b0011; // 3
B = 4'b0100; // 4
sum = A + B; // sum = 4'b0111 (7)
product = A * B; // product = 8'd12
// 如果操作数中有 x
in1 = 4'b101x;
in2 = 4'b1010;
sum = in1 + in2; // sum = 4'bxxxx(结果不确定)
注意 :/ 和 % 操作符的第二个操作数不能为 0,否则结果为 x(不确定)。
二、逻辑操作符(Logical Operators)
| 操作符 | 功能 | 示例 |
|---|---|---|
! |
逻辑非 | !a |
&& |
逻辑与 | a && b |
| ` | ` |
规则:
-
结果为 1 位:
0(假)、1(真)、x(不确定) -
任何非零值等价于逻辑
1 -
任何值为 0 等价于逻辑
0reg [3:0] A, B;
A = 4'b0011; // 非零 → 逻辑1
B = 4'b0000; // 零 → 逻辑0A && B // 结果 = 0(1 && 0 = 0)
A || B // 结果 = 1(1 || 0 = 1)
!A // 结果 = 0(逻辑1取反)// 有未知值
A = 2'b0x;
B = 2'b10;
A && B // 结果 = x(不确定 && 真 = 不确定)
逻辑操作符 vs 按位操作符 :逻辑操作符(&&、||、!)返回 1 位结果,按位操作符(&、|、~)返回与操作数等宽的结果。
三、关系操作符(Relational Operators)
| 操作符 | 功能 |
|---|---|
> |
大于 |
< |
小于 |
>= |
大于等于 |
<= |
小于等于 |
A = 4; B = 3;
A > B // 结果 = 1(真)
A <= B // 结果 = 0(假)
X = 4'b1010;
Y = 4'b1xxz;
Y < X // 结果 = x(有不确定值)
<= 的歧义 :<= 在表达式中是关系操作符(小于等于),在过程赋值语句中是非阻塞赋值符。上下文决定其含义。
四、等价操作符(Equality Operators)
| 操作符 | 功能 | 说明 |
|---|---|---|
== |
逻辑相等 | 有 x/z 时结果为 x |
!= |
逻辑不等 | 有 x/z 时结果为 x |
=== |
Case 相等 | 精确比较 ,包括 x 和 z |
!== |
Case 不等 | 精确比较 ,包括 x 和 z |
X = 4'b1010;
Y = 4'b1101;
Z = 4'b1xxz;
M = 4'b1xxz;
X == Y // 结果 = 0
X != Y // 结果 = 1
Z == M // 结果 = 1(=== 的精确比较)
Z === M // 结果 = 1(包括 x 和 z 也完全匹配)
// 逻辑相等 vs case相等
X = 4'b1010;
Y = 4'b101x;
X == Y // 结果 = x(有x,不确定)
X === Y // 结果 = 0(1010 ≠ 101x)
何时使用 === :当需要判断信号是否为 x 或 z 时,必须使用 ===。例如:if (state === 3'bxxx)。
五、按位操作符(Bitwise Operators)
| 操作符 | 功能 | 示例 |
|---|---|---|
~ |
按位取反 | ~a |
& |
按位与 | a & b |
| ` | ` | 按位或 |
^ |
按位异或 | a ^ b |
^~ 或 ~^ |
按位同或 | a ^~ b |
X = 4'b1010;
Y = 4'b1101;
~X // 结果 = 4'b0101
X & Y // 结果 = 4'b1000
X | Y // 结果 = 4'b1111
X ^ Y // 结果 = 4'b0111
X ^~ Y // 结果 = 4'b1000
六、缩减操作符(Reduction Operators)
| 操作符 | 功能 | 示例 |
|---|---|---|
& |
缩减与 | &a |
~& |
缩减与非 | ~&a |
| ` | ` | 缩减或 |
| `~ | ` | 缩减或非 |
^ |
缩减异或 | ^a |
^~ |
缩减同或 | ^~a |
与按位操作符的区别:
-
按位操作符:两个操作数的对应位进行运算,结果与操作数等宽
-
缩减操作符:一个操作数 ,所有位逐位从左到右运算,结果为 1 位
X = 4'b1010;
&X // 结果 = 1 & 0 & 1 & 0 = 0
|X // 结果 = 1 | 0 | 1 | 0 = 1
^X // 结果 = 1 ^ 0 ^ 1 ^ 0 = 0// 实际应用:奇偶校验
wire parity;
assign parity = ^data_bus; // data_bus所有位的异或 = 奇偶校验位
💡 缩减异或的应用 :^data_bus 常用于生成奇偶校验位。如果所有位异或结果为 0,说明 1 的个数为偶数;为 1 则说明 1 的个数为奇数。
七、移位操作符(Shift Operators)
| 操作符 | 功能 |
|---|---|
<< |
左移(低位补0) |
>> |
右移(高位补0) |
<<< |
算术左移(符号位扩展) |
>>> |
算术右移(符号位扩展) |
X = 4'b1100;
X >> 1 // 结果 = 4'b0110(右移1位,高位补0)
X << 1 // 结果 = 4'b1000(左移1位,低位补0)
X << 2 // 结果 = 4'b0000(左移2位,低位补0)
integer a, b, c;
a = 0;
b = -10;
c = a + (b >>> 3); // 算术右移,符号位扩展,结果 = -2
八、拼接操作符(Concatenation Operator)
| 操作符 | 功能 |
|---|---|
{ } |
将多个操作数拼接成一个操作数 |
A = 1'b1;
B = 2'b00;
C = 2'b10;
D = 3'b110;
Y = {B, C}; // 结果 = 4'b0010
Y = {A, B, C, D}; // 结果 = 11'b10010110
Y = {A, B[0], C[1]}; // 结果 = 3'b101
拼接操作的每个操作数都必须有确定的位宽 ,不能使用无位宽的数(如 'hA)。
九、重复操作符(Replication Operator)
| 操作符 | 功能 |
|---|---|
{n{expr}} |
将表达式 expr 重复 n 次 |
A = 1'b1;
B = 2'b00;
Y = {4{A}}; // 结果 = 4'b1111
Y = {4{A}, 2{B}}; // 结果 = 8'b11110000
Y = {4{A}, 2{B}, C}; // 结果 = 10'b1111000010
// 清空寄存器
reg [7:0] data;
data = {8{1'b0}}; // data = 8'b00000000
// 生成特定模式
reg [15:0] pattern = {8{2'b10}}; // 16'b1010101010101010
十、条件操作符(Conditional Operator)
| 操作符 | 功能 |
|---|---|
?: |
条件选择 |
语法:
<条件表达式> ? <真表达式> : <假表达式>
// 二选一多路选择器
assign out = sel ? in1 : in0;
// 三态缓冲器
assign bus = enable ? data_out : 36'bz;
// 嵌套条件表达式(四选一)
assign out = (A == 3) ? (sel ? x : y) : (sel ? m : n);
条件操作符的本质:等价于硬件中的多路选择器(MUX)。综合工具会将其实现为多路选择器电路。
6.5 综合实例
一、四选一多路选择器(数据流实现)
方法1:使用逻辑等式
module mux4_to_1(out, i0, i1, i2, i3, s1, s0);
output out;
input i0, i1, i2, i3;
input s1, s0;
assign out = (~s1 & ~s0 & i0) |
(~s1 & s0 & i1) |
( s1 & ~s0 & i2) |
( s1 & s0 & i3);
endmodule
方法2:使用条件操作符(最简洁)
module mux4_to_1(out, i0, i1, i2, i3, s1, s0);
output out;
input i0, i1, i2, i3;
input s1, s0;
assign out = s1 ? (s0 ? i3 : i2) : (s0 ? i1 : i0);
endmodule
对比门级实现:数据流实现(4行代码)vs 门级实现(16行代码),数据流建模的效率优势非常明显。
二、四位全加器(数据流实现)
module fulladd4(sum, c_out, a, b, c_in);
output [3:0] sum;
output c_out;
input [3:0] a, b;
input c_in;
assign {c_out, sum} = a + b + c_in;
endmodule
一行代码实现四位全加器 !这就是数据流建模的强大之处。{c_out, sum} 将进位和结果拼接成 5 位数,接收 4 位加法 + 进位的结果。
三、四位超前进位加法器
脉动进位加法器存在进位逐级传递的延迟问题。超前进位加法器通过提前计算进位来减少延迟。
module fulladd4(sum, c_out, a, b, c_in);
output [3:0] sum;
output c_out;
input [3:0] a, b;
input c_in;
wire p0, g0, p1, g1, p2, g2, p3, g3;
wire c1, c2, c3, c4;
// 计算P(传播)和G(生成)
assign p0 = a[0] ^ b[0];
assign g0 = a[0] & b[0];
assign p1 = a[1] ^ b[1];
assign g1 = a[1] & b[1];
assign p2 = a[2] ^ b[2];
assign g2 = a[2] & b[2];
assign p3 = a[3] ^ b[3];
assign g3 = a[3] & b[3];
// 超前进位计算(所有进位同时计算)
assign c1 = g0 | (p0 & c_in);
assign c2 = g1 | (p1 & g0) | (p1 & p0 & c_in);
assign c3 = g2 | (p2 & g1) | (p2 & p1 & g0) | (p2 & p1 & p0 & c_in);
assign c4 = g3 | (p3 & g2) | (p3 & p2 & g1) | (p3 & p2 & p1 & g0) | (p3 & p2 & p1 & p0 & c_in);
// 计算和
assign sum[0] = p0 ^ c_in;
assign sum[1] = p1 ^ c1;
assign sum[2] = p2 ^ c2;
assign sum[3] = p3 ^ c3;
assign c_out = c4;
endmodule
超前进位的优势:所有进位同时计算,延迟固定为 4 级门延迟,与位数无关。但代价是逻辑更复杂,面积更大。
6.6 操作符优先级
为了避免优先级混淆,建议使用括号明确运算顺序。
| 优先级 | 操作符 |
|---|---|
| 最高 | + - ! ~(单目) |
| ↓ | * / % |
| ↓ | + -(双目) |
| ↓ | << >> |
| ↓ | < <= > >= |
| ↓ | == != === !== |
| ↓ | &(按位与) |
| ↓ | ^ ^~(按位异或/同或) |
| ↓ | ` |
| ↓ | &&(逻辑与) |
| ↓ | ` |
| 最低 | ?:(条件) |
// 不清晰(依赖优先级)
assign out = a & b | c ^ d;
// 清晰(使用括号)
assign out = (a & b) | (c ^ d);
经验法则 :除非是最简单的 a & b,否则一律使用括号明确优先级。这不仅让代码意图清晰,还能避免因记错优先级而导致的逻辑错误。