Menu

Verilog 数字字面量:二进制、十六进制、十进制与定宽常量

Verilog 如何书写常量:定宽与不定宽,'b 'h 'd 'o 四种进制,有符号数,用下划线提高可读性,以及让初学者栽跟头的陷阱。

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

定宽字面量的构成

一个 Verilog 字面量最多由三部分组成:

8'b1010_1100
│  │ │
│  │ └─ digits in the chosen base
│  └─── base specifier: b (binary), h (hex), d (decimal), o (octal)
└────── width in bits (decimal)

从左往右读:“一个 8 位的值,用二进制书写,数字为 10101100”。它在十进制中是 172,十六进制中是 0xAC,无论怎么写,位都是一样的(用 进制转换器 可以并排看到这三种表示)。宽度告诉编译器要分配多少位;进制告诉它如何解读这些数字。

下划线是视觉上的分隔符。32'b1010_1100_0011_0101 和 32'b1010110000110101 是一样的。多用它们。

四种进制

超过 4 位的值,十六进制(h)是标准写法。想直接看位模式时,用二进制(b)。表示对人有意义的数量时,用十进制(d)。八进制(o)虽然存在,但你几乎不会见到。

十六进制数字不区分大小写:8'hAB 和 8'hab 完全相同。

值放不下时会怎样?

如果数字没有填满声明的宽度,Verilog 会在左侧补零:

8'b101       // equivalent to 8'b00000101  (= 5)
8'hF         // equivalent to 8'h0F        (= 15)

如果数字超出了声明的宽度,Verilog 会截掉高位并给出警告:

4'hFF        // truncates to 4'hF, simulator warns
4'b10000     // truncates to 4'b0000

这个警告是在帮你。不要屏蔽它。

定宽与不定宽

没有宽度的字面量:

'd10    // unsized, takes default size (≥32 bits)
10      // also unsized - no base means decimal

在大多数工具中,不定宽字面量默认为 32 位。这是一整类 bug 的根源:

reg [7:0] count;
if (count == -1) ...      // -1 is unsized 32-bit; comparison gets weird

解决办法是用 8'hFF 代替 -1,或者写 8'd255、{8{1'b1}}。在对位宽敏感的表达式中使用字面量时,一定要写明宽度。

唯一可以放心使用不定宽字面量的地方,是测试平台 for 循环中的整数计数器,因为 integer 足够宽,不会出什么意外。

有符号字面量

定宽字面量默认是无符号的:

8'd255   // unsigned 255
8'b1111_1111   // unsigned 255 - same bit pattern, just written differently

如果希望字面量按有符号数解读,在撇号后面加上 s:

8'sd10           // signed 10
8'sb1111_1111    // signed -1 (two's complement)

s 标志在这些地方会产生影响:

  • <<< 和 >>> 运算符(算术移位):它们会对有符号操作数做符号扩展。
  • 有符号混合表达式中的比较运算符。
  • $signed/$unsigned 类型转换。

对于总线信号上的大多数普通算术运算,不需要加 s。

包含 X 和 Z 的数字字面量

字面量中的数字可以是 x(未知)或 z(高阻):

8'bx 是“全部 8 位都未知”的简写:这个值会被扩展到填满声明的宽度。x 数字常见于状态机的 default 分支和初始化中。具体语义在 X 与 Z 值 中讲解。

定宽字面量的实际用法

一旦开始编写真正的模块,定宽字面量就无处不在:

// Bus widths
input  wire [31:0] addr;
wire [31:0] zero = 32'h0;
wire [31:0] all_ones = 32'hFFFF_FFFF;

// State encodings
localparam IDLE = 3'd0;
localparam BUSY = 3'd1;
localparam DONE = 3'd2;

// Masks
wire is_msb_set = data & 32'h8000_0000;

// Comparisons
if (counter == 8'd255) ...

// Reset values
always @(posedge clk) begin
    if (reset) data_out <= 8'd0;
    else       data_out <= data_in;
end

模式始终如一:写宽度,写进制,写数字。你用到的每个字面量都是这个样子。

动手试试

现在,Verilog 模块可能需要的任何常量你都能写出来了。数据类型的最后一部分,讲的是信号不是干净的 0 或 1 时会发生什么:x 和 z 值。

常见问题

Verilog 中的 8'b1010 是什么意思?

这是一个定宽的二进制字面量:一个 8 位的值,二进制表示为 00001010。撇号前面的数字是以位为单位的宽度;撇号后面的字母是进制(b 二进制、h 十六进制、d 十进制、o 八进制);再后面的数字就是值。如果数字位数填不满宽度,字面量会在左侧补零。

Verilog 中定宽数和不定宽数有什么区别?

8'd10 这样的定宽字面量正好 8 位宽。'd10 或者干脆写 10 这样的不定宽字面量默认是 32 位,通常太多了。在对位宽敏感的表达式中混用不定宽字面量会引发不易察觉的 bug:除了临时的测试代码,其他地方都优先使用定宽字面量。

Verilog 中如何书写十六进制数?

使用 'h 进制说明符:8'hFF 是一个值为 255 的 8 位数。十六进制数字不区分大小写,8'hff 和 8'hFF 完全相同。可以用下划线给数字分组,提高可读性:32'hDEAD_BEEF。解析器会忽略这些下划线。

Verilog 数字中的下划线有什么作用?

下划线是视觉上的分隔符,解析器会忽略它们。32'b1010_1100_0011_0101 和 32'b1010110000110101 的值完全一样,但前者好读得多。进制前缀之后的第一个字符不能是下划线,除此之外,数字串中的任何位置都可以放。

Verilog 中如何书写有符号数?

在撇号后面加上 s:8'sd10 是有符号的 8 位十进制数 10,8'sb1111_1111 是有符号的 -1。定宽字面量默认是无符号的,s 修饰符会改变这一点。大多数算术运算使用无符号操作数;只有当你确实需要负值在 <、>>> 等运算符中正确传递时,才使用有符号数。

Coddy 编程语言插图

和 Coddy 一起学编程

开始