第 3 章:数据类型与运算符¶
从"变量"到"信号"——理解硬件世界的数据表示
章节概览¶
| 小节 | 内容 | 核心概念 |
|---|---|---|
| 3.1 | 四值逻辑系统 | 0、1、x、z 的含义与应用 |
| 3.2 | wire 与 reg | 连线类型与寄存器类型的本质区别 |
| 3.3 | 向量(Vector) | 多位宽信号的定义与操作 |
| 3.4 | 数组(Array) | 存储器建模与多维数组 |
| 3.5 | 运算符 | 算术、逻辑、位运算、拼接、条件 |
| 3.6 | 本章练习 | 数据类型与运算符综合练习 |
3.1 四值逻辑系统¶
不只是 0 和 1¶
Verilog 的信号除了 0 和 1,还有两种特殊值:
| 值 | 含义 | 场景 | 乐高比喻 |
|---|---|---|---|
| 0 | 逻辑低电平 | 正常低电平信号 | 积木凹槽(空) |
| 1 | 逻辑高电平 | 正常高电平信号 | 积木凸起(有) |
| x | 未知值 | 未初始化的寄存器、多驱动冲突 | 不确定有没有凸起 |
| z | 高阻态 | 三态门输出、未连接的端口 | 积木没放上去(悬空) |
x 和 z 的实际意义
- x(未知) :仿真开始时,
reg类型变量的默认值就是x。这提醒你"这个信号还没有被赋予确定的值"。 - z(高阻) :相当于"断开连接"。在总线设计中,多个设备共享同一根线时,不发言的设备输出
z,避免信号冲突。
四值逻辑的运算规则¶
// 与运算中的 x 和 z
0 & x = 0 // 与 0 相与,结果一定是 0
1 & x = x // 与 1 相与,结果取决于 x
x & x = x // 两个未知,结果未知
// 或运算中的 x 和 z
1 | x = 1 // 与 1 相或,结果一定是 1
0 | x = x // 与 0 相或,结果取决于 x
3.2 wire 与 reg¶
最容易混淆的概念¶
这是 Verilog 初学者最常问的问题: wire 和 reg 到底有什么区别?
关键认知
wire 和 reg 不是"数据类型"的区别,而是"驱动方式"的区别 。它们不决定信号是"组合逻辑"还是"时序逻辑"。
| 特性 | wire | reg |
|---|---|---|
| 默认值 | z(高阻) |
x(未知) |
| 驱动方式 | 持续赋值(assign)或端口连接 |
过程赋值(always 块内) |
| 存储能力 | 不存储值,只是连线 | 可以保持值(在 always 块中) |
| 使用位置 | assign 语句、模块端口连接 |
always 块内部 |
| 乐高比喻 | 导线——只负责传导信号 | 带锁的盒子——可以存放信号值 |
使用场景对照表¶
// wire 的典型用法
wire [7:0] data_bus; // 8 位数据总线
assign data_bus = a + b; // wire 用 assign 持续赋值
// reg 的典型用法
reg [7:0] counter; // 8 位计数器
always @(posedge clk) // reg 在 always 块中被赋值
counter <= counter + 1;
简单记忆法
- 在
assign语句左边 → 用wire - 在
always块中被赋值 → 用reg - 模块的
input端口 → 默认wire(可省略) - 模块的
output端口 → 看情况:assign驱动用wire,always驱动用reg
3.3 向量(Vector)¶
多位宽信号¶
现实中的数字电路很少只处理 1 位信号。向量用于表示多位宽的信号:
wire [7:0] data; // 8 位宽连线,data[7] 是最高位
reg [15:0] address; // 16 位宽寄存器
wire [0:7] reversed; // 8 位宽,但 data[0] 是最高位(不推荐)
乐高比喻: 1 位信号 = 1×1 积木块。向量 = 1×8 长条积木——8 个位并排在一起,作为一个整体使用。
位选择与部分选择¶
wire [7:0] byte_data; // 8 位向量
// 位选择:取单个位
wire bit7 = byte_data[7]; // 取最高位
wire bit0 = byte_data[0]; // 取最低位
// 部分选择:取连续的一段
wire [3:0] high_nibble = byte_data[7:4]; // 取高 4 位
wire [3:0] low_nibble = byte_data[3:0]; // 取低 4 位
实战:8 位加法器
3.4 数组(Array)¶
存储器建模¶
数组用于建模存储器(如 RAM、ROM):
// 声明一个 32 个单元的存储器,每个单元 8 位宽
reg [7:0] memory [0:31];
// 读写操作
memory[0] = 8'hA5; // 向地址 0 写入 0xA5
memory[15] = 8'h3C; // 向地址 15 写入 0x3C
向量 vs 数组
reg [7:0] data;—— 向量 :一个 8 位宽的寄存器reg data [0:7];—— 数组 :8 个 1 位宽的寄存器reg [7:0] mem [0:31];—— 向量数组 :32 个 8 位宽的寄存器(存储器)
多维数组¶
// 二维数组:4 行 × 8 列的存储器
reg [7:0] matrix [0:3][0:7];
// 三维数组(谨慎使用,综合工具支持有限)
reg [7:0] cube [0:3][0:3][0:3];
3.5 运算符¶
运算符总览¶
| 类别 | 运算符 | 示例 | 说明 |
|---|---|---|---|
| 算术 | + - * / % |
a + b |
加减乘除取模 |
| 位运算 | & \| ^ ~ |
a & b |
按位与/或/异或/非 |
| 逻辑 | && \|\| ! |
a && b |
逻辑与/或/非(结果为 1 位) |
| 关系 | > < >= <= |
a > b |
大于/小于/大于等于/小于等于 |
| 相等 | == != |
a == b |
等于/不等于(x 和 z 参与比较) |
| 全等 | === !== |
a === b |
全等/不全等(x 和 z 也精确匹配) |
| 移位 | << >> |
a << 2 |
逻辑左移/右移 |
| 拼接 | {} |
{a, b} |
将多个信号拼接在一起 |
| 复制 | {{}} |
{4{a}} |
将信号复制多次 |
| 条件 | ? : |
sel ? a : b |
三目条件运算符 |
位运算 vs 逻辑运算¶
这是初学者最容易混淆的地方:
wire [3:0] a = 4'b1010; // 4 位:1010
wire [3:0] b = 4'b0101; // 4 位:0101
// 位运算:对每一位分别操作,结果仍是 4 位
wire [3:0] bit_and = a & b; // 结果:4'b0000(每位分别与)
wire [3:0] bit_or = a | b; // 结果:4'b1111(每位分别或)
// 逻辑运算:将整个操作数视为真/假,结果只有 1 位
wire logic_and = a && b; // a 非零(真) && b 非零(真) = 1
wire logic_or = a || b; // a 非零(真) || b 非零(真) = 1
常见错误
把 & 写成 && 或反之,是 Verilog 中最常见的 bug 之一。记住:
- 位运算(& |)→ 结果是多位宽
- 逻辑运算(&& ||)→ 结果只有 1 位
拼接运算符¶
拼接是 Verilog 中最常用的运算符之一:
wire [3:0] a = 4'b1010;
wire [3:0] b = 4'b0101;
// 基本拼接
wire [7:0] concat = {a, b}; // 结果:8'b1010_0101
// 拼接 + 部分选择
wire [5:0] mixed = {a[3:2], b}; // 结果:6'b10_0101
// 复制拼接
wire [7:0] dup = {4{2'b10}}; // 结果:8'b10101010
// 等价于 {2'b10, 2'b10, 2'b10, 2'b10}
条件运算符¶
条件运算符用于实现二选一多路选择器:
// 语法:条件 ? 真值 : 假值
assign y = sel ? a : b; // sel=1 时 y=a,sel=0 时 y=b
// 嵌套条件运算符(实现多路选择)
assign y = sel[1] ? (sel[0] ? d3 : d2)
: (sel[0] ? d1 : d0);
// sel=11→d3, sel=10→d2, sel=01→d1, sel=00→d0
3.6 本章练习¶
基础练习¶
-
声明一个 16 位宽的
wire类型信号data_bus,并声明一个 8 位宽的reg类型信号counter。 -
写出以下表达式的值(
a = 4'b1100,b = 4'b1010): a & ba | ba ^ b~a{a[3:2], b[1:0]}
代码练习¶
-
编写一个 4 位宽的二选一多路选择器模块,使用条件运算符实现。
-
编写一个 8 位宽的位反转模块(输入
8'b1100_0011,输出8'b1100_0011的位序反转)。 -
使用拼接运算符实现一个 8 位循环左移模块(输入左移 2 位,移出的位补到右侧)。
仿真验证¶
- 为练习 3 的多路选择器编写 Testbench,覆盖所有输入组合,用 GTKWave 验证波形。
FAQ:常见问题解答¶
wire 和 reg 到底怎么选?
记住口诀: assign 左边用 wire,always 里面用 reg 。更准确地说:在 assign 语句和模块端口连接中,被驱动的信号必须是 wire 类型;在 always 块中被赋值的信号必须是 reg 类型。
x 和 z 能综合成实际电路吗?
x(未知值) 不能综合 ——它只是仿真中的概念,表示"不确定"。实际电路中不存在"未知"的电平。z(高阻态) 可以综合 ——对应三态门的输出。在 FPGA 中,只有特定引脚支持三态输出。
== 和 === 有什么区别?
==:逻辑相等比较,如果操作数中有x或z,结果为x(不确定)。===:全等比较,x和z也参与精确匹配。1'b1 === 1'b1为真,1'b1 === 1'bx为假。- 在可综合代码中, 只用
==和!=。===和!==仅用于仿真。
拼接运算符 {} 中每个元素的位置数必须确定吗?
是的。 拼接中的每个元素必须有确定的位宽。不能拼接位宽不确定的表达式。例如 {a, b+c} 中 b+c 的位宽由操作数位宽决定,这是允许的。
下一章预告: 在第 4 章中,我们将学习 Verilog 最核心的概念——模块与端口,理解如何用"乐高积木"的方式搭建层次化的数字电路。