基础语法
本章介绍 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
);
注释最佳实践
- 文件头注释:每个文件开头应包含文件说明、作者、日期等信息
//============================================================================
// Module Name: counter
// Description: 8位同步计数器
// Author: Your Name
// Date: 2024-01-01
// Version: 1.0
//============================================================================
- 功能说明注释:重要的功能块应添加说明
// 状态机状态定义
localparam IDLE = 2'b00;
localparam READ = 2'b01;
localparam WRITE = 2'b10;
localparam DONE = 2'b11;
- 端口说明注释:复杂的端口应添加功能说明
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 | 时间 |
| %c | ASCII 字符 |
| %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 的数据类型。