跳转至

第 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 到底有什么区别?

关键认知

wirereg 不是"数据类型"的区别,而是"驱动方式"的区别 。它们不决定信号是"组合逻辑"还是"时序逻辑"。

特性 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 驱动用 wirealways 驱动用 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 位加法器

module adder_8bit (
    input  wire [7:0] a,     // 8 位输入 a
    input  wire [7:0] b,     // 8 位输入 b
    output wire [7:0] sum,   // 8 位和
    output wire       carry  // 进位输出
);

    // {carry, sum} 是拼接操作:9 位 = 1 位进位 + 8 位和
    assign {carry, sum} = a + b;

endmodule

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 本章练习

基础练习

  1. 声明一个 16 位宽的 wire 类型信号 data_bus,并声明一个 8 位宽的 reg 类型信号 counter

  2. 写出以下表达式的值(a = 4'b1100, b = 4'b1010):

  3. a & b
  4. a | b
  5. a ^ b
  6. ~a
  7. {a[3:2], b[1:0]}

代码练习

  1. 编写一个 4 位宽的二选一多路选择器模块,使用条件运算符实现。

  2. 编写一个 8 位宽的位反转模块(输入 8'b1100_0011,输出 8'b1100_0011 的位序反转)。

  3. 使用拼接运算符实现一个 8 位循环左移模块(输入左移 2 位,移出的位补到右侧)。

仿真验证

  1. 为练习 3 的多路选择器编写 Testbench,覆盖所有输入组合,用 GTKWave 验证波形。

FAQ:常见问题解答

wire 和 reg 到底怎么选?

记住口诀: assign 左边用 wire,always 里面用 reg 。更准确地说:在 assign 语句和模块端口连接中,被驱动的信号必须是 wire 类型;在 always 块中被赋值的信号必须是 reg 类型。

xz 能综合成实际电路吗?

  • x(未知值) 不能综合 ——它只是仿真中的概念,表示"不确定"。实际电路中不存在"未知"的电平。
  • z(高阻态) 可以综合 ——对应三态门的输出。在 FPGA 中,只有特定引脚支持三态输出。

===== 有什么区别?

  • ==:逻辑相等比较,如果操作数中有 xz,结果为 x(不确定)。
  • ===:全等比较,xz 也参与精确匹配。1'b1 === 1'b1 为真,1'b1 === 1'bx 为假。
  • 在可综合代码中, 只用 ==!====!== 仅用于仿真。

拼接运算符 {} 中每个元素的位置数必须确定吗?

是的。 拼接中的每个元素必须有确定的位宽。不能拼接位宽不确定的表达式。例如 {a, b+c}b+c 的位宽由操作数位宽决定,这是允许的。


下一章预告: 在第 4 章中,我们将学习 Verilog 最核心的概念——模块与端口,理解如何用"乐高积木"的方式搭建层次化的数字电路。

继续第 4 章:模块与端口 →