跳到主要内容

基础语法

本章介绍 Verilog 的基础语法,包括模块定义、端口声明、注释规则等核心概念。

模块定义

模块(module)是 Verilog 的基本设计单元,所有的设计都封装在模块中。一个模块代表一个电路单元,可以是简单的门电路,也可以是复杂的处理器。

模块基本结构

module module_name(
// 端口声明
input [width-1:0] input_port,
output [width-1:0] output_port
);
// 模块内部逻辑
// ...
endmodule

Verilog-2001 端口声明方式

Verilog-2001 标准支持在端口列表中直接声明端口类型和位宽,这是推荐的写法:

module adder(
input [7:0] a, // 8位输入
input [7:0] b, // 8位输入
output [8:0] sum // 9位输出(考虑进位)
);
assign sum = a + b;
endmodule

Verilog-1995 端口声明方式

旧标准需要在模块内部单独声明端口类型:

module adder(a, b, sum);
input [7:0] a;
input [7:0] b;
output [8:0] sum;

assign sum = a + b;
endmodule

本教程推荐使用 Verilog-2001 的声明方式,更加简洁清晰。

模块命名规则

模块名称需要遵循以下规则:

  • 只能包含字母、数字、下划线和美元符号
  • 必须以字母或下划线开头
  • 区分大小写
  • 不能使用 Verilog 关键字
  • 建议使用有意义的名称
// 合法的模块名
module counter_8bit;
module UART_TX;
module fifo_buffer;

// 不合法的模块名
module 8bit_counter; // 不能以数字开头
module counter-8bit; // 不能包含连字符
module module; // 不能使用关键字

端口类型

Verilog 定义了三种端口类型:

输入端口(input)

输入端口用于接收外部信号,在模块内部只能读取,不能赋值。

module and_gate(
input a, // 1位输入
input b, // 1位输入
output y // 1位输出
);
assign y = a & b;
endmodule

输出端口(output)

输出端口用于向外部输出信号,可以声明为 wire 或 reg 类型。

module d_flip_flop(
input clk,
input d,
output reg q // reg 类型的输出端口
);
always @(posedge clk) begin
q <= d;
end
endmodule

双向端口(inout)

双向端口可以在不同时刻作为输入或输出使用,常用于总线接口。

module bidirectional_bus(
inout [7:0] data_bus,
input output_enable,
input [7:0] data_out,
output [7:0] data_in
);
assign data_bus = output_enable ? data_out : 8'bz;
assign data_in = data_bus;
endmodule

注释

Verilog 支持两种注释方式:

单行注释

使用 // 开始,到行尾结束:

// 这是一个单行注释
assign y = a & b; // 行尾注释

多行注释

使用 /* */ 包围,可以跨越多行:

/*
* 这是一个多行注释
* 可以跨越多行
*/
module counter(
input clk,
input reset,
output [7:0] count
);

注释最佳实践

  1. 文件头注释:每个文件开头应包含文件说明、作者、日期等信息
//============================================================================
// Module Name: counter
// Description: 8位同步计数器
// Author: Your Name
// Date: 2024-01-01
// Version: 1.0
//============================================================================
  1. 功能说明注释:重要的功能块应添加说明
// 状态机状态定义
localparam IDLE = 2'b00;
localparam READ = 2'b01;
localparam WRITE = 2'b10;
localparam DONE = 2'b11;
  1. 端口说明注释:复杂的端口应添加功能说明
module uart_tx(
input clk, // 系统时钟
input reset_n, // 异步复位,低有效
input [7:0] tx_data, // 待发送数据
input tx_start, // 发送启动信号
output tx_busy, // 发送忙标志
output tx_done, // 发送完成标志
output uart_tx // UART 发送引脚
);

标识符

命名规则

Verilog 标识符的命名规则:

  • 可以包含字母(a-z, A-Z)、数字(0-9)、下划线(_)和美元符号($)
  • 必须以字母或下划线开头
  • 区分大小写
  • 最大长度为 1024 个字符
// 合法的标识符
wire data_bus;
wire _internal_signal;
wire clock$1;

// 不合法的标识符
wire 2data; // 不能以数字开头
wire data-bus; // 不能包含连字符
wire wire; // 不能使用关键字

转义标识符

使用反斜杠可以定义包含特殊字符的标识符:

wire \data-bus ;     // 包含连字符
wire \clock(1) ; // 包含括号
wire \a*b ; // 包含星号

转义标识符以反斜杠开头,以空白字符(空格、制表符、换行符)结束。

命名约定

良好的命名约定可以提高代码可读性:

// 推荐的命名风格

// 信号命名:小写字母,下划线分隔
wire data_valid;
wire clock_enable;

// 常量命名:大写字母,下划线分隔
localparam DATA_WIDTH = 8;
localparam CLOCK_FREQ = 50_000_000;

// 端口命名:小写字母,下划线分隔
input clk;
input reset_n; // _n 表示低有效

// 用户定义类型:大驼峰命名
typedef enum logic [2:0] {
StateIdle,
StateRead,
StateWrite
} StateType;

数值表示

Verilog 支持多种数值表示方式。

整数表示

基本格式:[位宽]'[进制][数值]

// 二进制
4'b1010 // 4位二进制数
8'b0000_1111 // 8位二进制数,使用下划线分隔提高可读性

// 十进制
8'd255 // 8位十进制数
16'd65535 // 16位十进制数

// 十六进制
8'hFF // 8位十六进制数
16'hABCD // 16位十六进制数

// 八进制
8'o377 // 8位八进制数

省略位宽

省略位宽时,默认为 32 位:

'd255        // 32位十进制数
'hFF // 32位十六进制数

省略进制和位宽

直接写数字时,默认为 32 位十进制数:

255          // 32位十进制数

负数表示

负数在数值前加负号:

-8'd127      // 负数
-4'b1001 // 负数

特殊值

// 高阻态
8'bz // 8位高阻态
4'bzzzz // 4位高阻态

// 不定态
8'bx // 8位不定态
4'bxxxx // 4位不定态

// 混合
8'b1010_xxxx // 高4位确定,低4位不定

字符串

Verilog 支持字符串常量,用双引号包围:

reg [8*13-1:0] message;

initial begin
message = "Hello, World!";
$display("%s", message);
end

字符串存储时,每个字符占用 8 位,按照从左到右的顺序存储。

reg [39:0] str;
initial begin
str = "HELLO"; // 存储为 48 45 4C 4C 4F(十六进制 ASCII 码)
end

系统任务

Verilog 提供了许多内置的系统任务,以 $ 开头:

显示任务

$display("Hello, World!");                    // 打印并换行
$write("Hello, World!"); // 打印不换行
$monitor("a = %d, b = %d", a, b); // 持续监控变量变化
$strobe("At time %t, a = %d", $time, a); // 在时间步结束时打印

格式化说明符

说明符含义
%d十进制
%b二进制
%h十六进制
%o八进制
%s字符串
%t时间
%cASCII 字符
%m模块层次路径

仿真控制

$finish;       // 结束仿真
$stop; // 暂停仿真
$time; // 当前仿真时间
$random; // 随机数

文件操作

integer fd;
fd = $fopen("output.txt", "w"); // 打开文件
$fdisplay(fd, "Hello, World!"); // 写入文件
$fclose(fd); // 关闭文件

编译指令

Verilog 提供了编译指令,以反引号(`)开头:

`define - 宏定义

`define DATA_WIDTH 8
`define MAX_VALUE 255

wire [`DATA_WIDTH-1:0] data;

`include - 文件包含

`include "defines.v"
`include "header.vh"

`timescale - 时间单位和精度

`timescale 1ns / 1ps    // 时间单位 1ns,精度 1ps

条件编译

`ifdef SIMULATION
// 仿真时代码
`else
// 综合时代码
`endif

`ifndef SYNTHESIS
// 非综合时代码
`endif

代码风格建议

缩进和对齐

使用一致的缩进风格(推荐 4 个空格):

module counter(
input clk,
input reset,
output reg [7:0] count
);
always @(posedge clk) begin
if (reset) begin
count <= 8'b0;
end else begin
count <= count + 1;
end
end
endmodule

端口对齐

多端口声明时保持对齐:

module uart_tx(
input clk, // 系统时钟
input reset_n, // 异步复位
input [7:0] tx_data, // 发送数据
input tx_start, // 发送启动
output tx_busy, // 发送忙标志
output uart_tx // UART 输出
);

模块实例化

使用命名端口连接方式:

// 推荐方式:命名端口连接
counter u_counter(
.clk (clk),
.reset (reset),
.count (count)
);

// 不推荐方式:位置端口连接
counter u_counter(clk, reset, count);

小结

本章介绍了 Verilog 的基础语法:

  • 模块定义和端口声明
  • 三种端口类型:input、output、inout
  • 单行和多行注释
  • 标识符命名规则
  • 数值表示方法
  • 字符串和系统任务
  • 编译指令

掌握这些基础语法后,就可以开始编写简单的 Verilog 模块了。下一章将详细介绍 Verilog 的数据类型。