Menu

Verilog 向量与数组:多位信号详解

如何用 [7:0] 声明多位信号、截取和拼接它们,以及 packed 向量和存储器数组之间的区别。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

一根线与多根线

到目前为止,我们见过的每个信号都只有 1 位宽。真实的设计几乎从来不是这样:地址是 16 位或 32 位,数据总线是 8 位或 64 位,RGB 像素是 24 位。Verilog 用同一种机制让任何信号变成多位:在方括号里加一个范围。

wire [7:0] data;       // 8-bit wire, bit 7 is MSB, bit 0 is LSB
reg  [15:0] address;   // 16-bit reg
output reg [31:0] result; // 32-bit module output

方括号中的数字是最高位和最低位的位编号。[7:0] 表示“这个信号的位从 7 编号到 0”,总共 8 位。第一个数是高位索引,第二个数是低位索引。

偶尔你会看到 [0:7]:位数相同,只是截取时的端序相反。[high:low] 是业界压倒性的惯例,除非有充分的理由,否则就用它。

实例:8 位加法器

每个 + 把两个 8 位向量相加,得到一个 9 位结果。像 8'd10 这样的数字字面量表示“一个 8 位宽、值为 10 的十进制数”,我们会在数字字面量中详细介绍。

截取:取出某些位

有了向量之后,就可以取出单个位或连续的一段:

不必在意 testbench 里的 force 那一行,我们只是需要一种注入值的方式来演示截取。真正有意思的是截取本身。

几条规则:

  • 截取方向必须与声明一致。如果声明的是 [7:0],就用 [high:low] 截取。方向反了会报语法错误。
  • 越界截取在仿真中会得到 x(未知)。综合工具可能给出警告或报错。
  • 位选择以你写的索引为准,从零开始:data[0] 就是编号为 0 的那一位,对于 [7:0] 的声明来说,它是最低位(LSB)。

起点可变的截取:+: 和 -:

一个常见需求是:“从第 N 位开始,给我 8 位”。你不能直接写 data[N+7:N],因为 Verilog 要求范围的两端都是常量。解决这个问题的语法是:

data[base +: width]   // width bits starting at `base`, going UP
data[base -: width]   // width bits starting at `base`, going DOWN

宽度是常量(我们每次取 8 位),但起点可以是运行时的表达式。按字节寻址的存储器、移位寄存器的抽头等场景需要的正是这个。

数组:比向量更进一步

向量是单个多位信号。数组则是一组可以分别索引的向量:

reg [31:0] mem [0:1023];

这个声明有两个范围,它们的含义不同:

  • [31:0] 是 packed 维度:每个字的宽度。
  • [0:1023] 是 unpacked 维度:一共有多少个字。

所以 mem 是 1024 个独立的 32 位寄存器。用一个索引访问其中之一:

mem[5] = 32'hCAFE_BABE;       // write word 5
data   = mem[address];        // read the word at `address`

这是一个存放平方数的小存储器。真实的设计用同样的写法实现寄存器堆、查找表、FIFO,以及其他比单个向量更大的片上存储。

packed 与 unpacked:为什么重要

packed 维度和 unpacked 维度的区分随处可见。分清两者能省下大量调试时间:

  • packed 向量是一个信号。 你可以把它整体当作一个数:data + 1 可以,data == 32'h0 可以,data[7:0] 也可以。
  • unpacked 数组是许多个信号。 你不能把它整体当作一个数:mem + 1 是语法错误。必须先取出某一个具体的字。

多个 packed 维度也是合法的:

reg [3:0][7:0] regs;   // 4 bytes packed together into a 32-bit signal

regs[0] 是一个字节(最低字节),regs 整体是 32 位。SystemVerilog 大量使用这种写法。

多个 unpacked 维度会构成一个二维存储器:

reg [31:0] frame [0:479][0:639];  // 480x640 of 32-bit pixels

用 frame[y][x] 访问单个像素。图像缓冲区在 HDL 中就是这个样子。

接下来

现在你可以声明并操作任意位宽的信号了。下一篇文档参数会介绍如何让这些位宽可配置,让同一个模块在一个实例中是 8 位,在另一个实例中是 32 位。之后我们会讲数字字面量的写法规则(8'b1010_1100、32'hDEAD_BEEF),以及信号没有被驱动时出现的 x/z 值。

常见问题

Verilog 中的向量是什么?

向量就是多位信号。给 wire 或 reg 加上一个范围就声明了向量:wire [7:0] data 是一个 8 位的 wire。方括号里的数字是位的编号,这里第 7 位是最高位,第 0 位是最低位。你可以截取单个位(data[3]),也可以截取连续的一段(data[7:4])。

Verilog 中的 [7:0] 是什么意思?

[7:0] 声明了从第 7 位到第 0 位(含两端)的范围,即一个以第 7 位为最高位的 8 位信号。第一个数是高位索引,第二个数是低位索引。也可以写成 [0:7],按小端顺序编号,但在业界代码中,[high:low] 这种写法远比它常见。

Verilog 中如何截取位?

用方括号索引。data[3] 取出单个位,data[7:4] 取出最高的四位,得到一个 4 位向量。截取的方向必须与声明一致:如果声明的是 [7:0],就用 [high:low] 截取。SystemVerilog 还增加了 data[3 +: 4],用于起点可变、宽度固定的截取。

Verilog 中 packed 数组和 unpacked 数组有什么区别?

packed 数组是一条连续的总线:reg [31:0] word 是一个 32 位信号。unpacked 数组(也叫“存储器”)是一组彼此独立的字:reg [31:0] mem [0:1023] 是 1024 个独立的 32 位寄存器。你可以读写 unpacked 数组中的一整个字,但不能把整个数组当作一个信号来运算。

Verilog 中如何声明存储器?

reg [31:0] mem [0:1023]; 声明了一个有 1024 项、每项 32 位宽的存储器。第一组方括号是字宽(packed),第二组是字的个数(unpacked)。用 mem[address] 访问某一项;启用 SystemVerilog-2005 的索引方式后,还可以用 mem[address][7:0] 读写该项中的一段位。

Coddy 编程语言插图

和 Coddy 一起学编程

开始