Verilog HDL学习笔记(六)| 第6章 数据流建模

6.1 连续赋值语句(Continuous Assignment)

一、什么是连续赋值?

连续赋值(Continuous Assignment) 是数据流建模的基础语句,用于驱动线网(wire)类型的变量。

核心理解: 连续赋值语句始终处于激活状态。只要右边表达式中的任何一个操作数发生变化,表达式就会被重新计算,结果立即(或在指定延迟后)赋给左边的线网变量。

语法:

复制代码
assign #延迟 线网名 = 表达式;

示例:

复制代码
wire out;
assign out = in1 & in2;          // out 始终等于 in1 AND in2

二、连续赋值的特点

特点 说明
始终激活 只要操作数变化,立即重新计算
左值必须是线网 赋值目标必须是 wire 类型,不能是 reg
右值可以是任何类型 可以是 wirereg、常数、函数调用等
延迟可选 可以指定赋值延迟,模拟电路传输延迟
复制代码
// 多个连续赋值语句
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 & baddr1 + 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 等价于逻辑 0

    reg [3:0] A, B;
    A = 4'b0011; // 非零 → 逻辑1
    B = 4'b0000; // 零 → 逻辑0

    A && 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 相等 精确比较 ,包括 xz
!== Case 不等 精确比较 ,包括 xz
复制代码
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)

何时使用 === :当需要判断信号是否为 xz 时,必须使用 ===。例如: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,否则一律使用括号明确优先级。这不仅让代码意图清晰,还能避免因记错优先级而导致的逻辑错误。

相关推荐
凯尔萨厮16 分钟前
Java学习笔记十二(异常与IO)
笔记·学习
一只旭宝17 分钟前
五种线程池设计
c++·笔记
冬夜戏雪1 小时前
学习日记-LRU缓存成员变量,入参day1
学习
深念Y1 小时前
Windows 11 工具链部署与 SSH 踩坑笔记
windows·笔记·ssh
Sunshing151 小时前
非自治连续系统稳定性定义与定理判据
笔记·学习
苏灵凯1 小时前
Codex 官网前端可以抄吗?从设计到实现的技术拆解
笔记·ai·agent·codex·deepseek
andrsted2 小时前
老师可以用小程序在线整理题库、课堂检测和错题集
学习·微信小程序·小程序·学习方法
zhangrelay3 小时前
手机-笔电-等数码产品-电池健康的一些数据-2026
笔记·学习·锂电池
苏子寒4 小时前
Nano-VLLM全代码解析笔记(10)-GemmaRMSNorm和MRoPE
笔记·机器学习·ai·nlp·vllm