当前位置: 首页 > news >正文

【Verilog】从零开始:Verilog基础语法全解析

1. Verilog基础语法概述

第一次接触Verilog时,我盯着代码看了半天——这玩意儿既像C语言又像电路图说明书。作为硬件描述语言(HDL),Verilog确实和我们熟悉的软件编程语言不太一样。它最神奇的地方在于:你写的每一行代码最终都会变成真实的电路元件。

举个例子,当你写下assign led = key;时,EDA工具会帮你生成一根实实在在的导线,把按键和LED灯直接连在一起。这种"代码即电路"的特性,让Verilog成为FPGA开发和ASIC设计的重要工具。我刚开始用Xilinx Vivado时,看着自己写的代码被综合成门级网表,那种感觉就像在玩电子版的乐高积木。

2. 基本语法元素

2.1 模块定义与端口声明

Verilog的世界是由模块(module)搭建的,每个模块就像一块积木。这是我早期写的一个LED闪烁模块:

module blink( input clk_50MHz, // 50MHz时钟输入 input rst_n, // 低电平复位 output reg led // LED输出 ); // 这里写具体实现 endmodule

几个注意点:

  • 模块名要有意义,我习惯用功能命名(比如uart_tx)
  • 信号方向除了input/output还有inout(双向端口)
  • 推荐使用_n表示低电平有效信号(如rst_n)
  • 位宽用[n-1:0]格式,比如input [7:0] data

2.2 标识符与命名规范

曾经因为大小写问题debug到凌晨两点——Verilog是区分大小写的!DataBusdatabus会被当成两个不同信号。这是我的命名习惯:

  • 模块名:小写+下划线(pwm_controller)
  • 常量:全大写(parameter CLK_DIV = 10;
  • 信号:小写+下划线(wire [15:0] data_out;
  • 时钟复位统一用clk/rst_n

避免使用关键字命名,比如别把模块命名为"module"或"reg"。

2.3 四种逻辑值

Verilog用四种状态描述电路:

  • 0:低电平(实际0V)
  • 1:高电平(比如3.3V)
  • x:未知状态(仿真时出现)
  • z:高阻态(三态门)

在仿真中遇到过x态传播问题:一个未初始化的寄存器会导致整个系统状态不确定。所以务必给所有寄存器明确初始值!

3. 数据类型详解

3.1 线网类型(wire)

wire就像电路板上的铜箔,用于连接各个元件。最常用的场景:

wire and_result = a & b; // 组合逻辑连线 assign or_result = a | b; // 另一种赋值方式

重要特性:

  • 默认值是z(高阻态)
  • 不能被过程块(always/initial)赋值
  • 可以用于模块端口连接

3.2 寄存器类型(reg)

reg并不完全对应硬件寄存器,它可以表示:

  • 时序逻辑中的触发器
  • 组合逻辑中的中间变量
reg [3:0] counter; // 真正的寄存器 always @(posedge clk) begin if(!rst_n) counter <= 0; else counter <= counter + 1; end reg comb_out; // 组合逻辑 always @(*) begin comb_out = a ^ b; end

3.3 参数与常量

参数让代码更灵活,比如设计可配置的分频器:

module clk_divider #( parameter DIV_RATIO = 10 // 默认分频比 )( input clk_in, output reg clk_out ); reg [7:0] cnt; always @(posedge clk_in) begin if(cnt == DIV_RATIO-1) begin cnt <= 0; clk_out <= ~clk_out; end else begin cnt <= cnt + 1; end end endmodule

实例化时可以修改参数:clk_divider #(.DIV_RATIO(20)) my_divider(...);

4. 运算符与表达式

4.1 位运算符

Verilog的位运算直接对应硬件门电路:

wire [3:0] a = 4'b1010; wire [3:0] b = 4'b1100; wire [3:0] c = ~a; // 按位取反:0101 wire [3:0] d = a & b; // 按位与:1000 wire [3:0] e = a | b; // 按位或:1110 wire [3:0] f = a ^ b; // 按位异或:0110

4.2 算术运算符

加减乘除在硬件中会综合成不同电路:

reg [7:0] sum = a + b; // 8位加法器 reg [15:0] prod = a * b;// 硬件乘法器

注意:除法和取余会消耗大量逻辑资源,在FPGA中要慎用。

4.3 移位运算

移位比乘除法更高效:

reg [7:0] a = 8'b1011_0011; wire [7:0] b = a << 2; // 左移:1100_1100 wire [7:0] c = a >> 1; // 逻辑右移:0101_1001 wire [7:0] d = a >>> 3; // 算术右移:1111_0110(符号位扩展)

4.4 拼接与复制

拼接运算符{}超实用:

wire [15:0] word = {byte_high, byte_low}; // 拼接字节 wire [31:0] extended = {16'd0, short_val};// 位扩展 wire [7:0] pattern = {8{1'b1}}; // 复制:11111111

5. 程序结构

5.1 连续赋值(assign)

assign最适合简单的组合逻辑:

assign out = sel ? a : b; // 2选1多路器 assign {cout, sum} = a + b + cin; // 全加器

5.2 过程块(always)

always块是Verilog的核心,有两种主要用法:

组合逻辑(敏感列表用*):

always @(*) begin if(en) y = a + b; else y = 0; end

时序逻辑(时钟触发):

always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt <= 0; end else begin cnt <= cnt + 1; end end

5.3 阻塞与非阻塞赋值

这是新手最容易踩的坑:

  • 阻塞赋值(=):用于组合逻辑,顺序执行
  • 非阻塞赋值(<=):用于时序逻辑,并行执行

看这个例子:

// 错误写法(会产生竞争) always @(posedge clk) begin a = b; b = a; // 不能交换a和b的值! end // 正确写法 always @(posedge clk) begin a <= b; b <= a; // 完美实现寄存器交换 end

6. 数字表示方法

6.1 四种进制格式

Verilog支持灵活的数值表示:

8'b1100_1011 // 二进制,下划线增强可读性 16'hABCD // 十六进制 24'd1000000 // 十进制 8'o377 // 八进制

6.2 位宽处理规则

位宽不匹配时:

  • 左边位宽 > 右边:高位补0
  • 左边位宽 < 右边:高位截断
wire [7:0] a = 4'b1101; // 自动补零:0000_1101 wire [3:0] b = 8'hFF; // 截断:1111

7. 代码风格建议

经过多个项目实践,我总结出这些经验:

  1. 模块划分:每个模块只实现单一功能
  2. 时钟管理:统一使用上升沿触发
  3. 复位策略:异步复位,同步释放
  4. 命名规范:信号名体现功能和极性
  5. 注释要求:每个模块头注明功能/作者/修改记录

这是我常用的状态机模板:

module fsm ( input clk, input rst_n, input [1:0] cmd, output reg [3:0] state ); // 状态编码 parameter IDLE = 4'b0001; parameter RUN = 4'b0010; parameter DONE = 4'b0100; // 状态寄存器 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin state <= IDLE; end else begin case(state) IDLE: if(cmd[0]) state <= RUN; RUN: if(cmd[1]) state <= DONE; DONE: state <= IDLE; default: state <= IDLE; endcase end end endmodule

学习Verilog就像学习一门新的思维方式——既要考虑代码逻辑,又要想象硬件结构。刚开始可能会不习惯,但当你第一次看到自己写的代码在开发板上跑起来时,那种成就感绝对值得付出。建议从简单项目入手,比如先实现一个PWM控制器,再逐步挑战UART、SPI等外设驱动。

http://www.jsqmd.com/news/620917/

相关文章:

  • RLCBuffer:面向Arduino的运行长度压缩缓冲区库
  • LangChain教程-、Langchain基础仍
  • 20260410AI日报
  • 2026Q2成都标识标牌技术落地:四川标识标牌、四川高炮广告、成都高炮广告、成都标识标牌、高炮广告、标识标牌选择指南 - 优质品牌商家
  • 手把手教你用Arduino驱动Goertek SPL06-007气压传感器(附完整代码与PCB布局避坑指南)
  • Python+scikit-fuzzy实战:5分钟搞定电机速度模糊控制(附完整代码)
  • .NET 诊断技巧 | 日志框架原理、手写日志框架学习略
  • Go语言中的命令行工具开发:从flag到cobra
  • 2026大师级小提琴品牌排行:实木小提琴、成人小提琴、收藏小提琴、欧料小提琴、油性漆小提琴、演奏独奏小提琴、纯手工小提琴选择指南 - 优质品牌商家
  • easyExcel动态行样式定制:从单行到多行的灵活控制
  • ESP32/ESP8266 LED点阵圣诞图标库:PxMatrix资源集成方案
  • lil_tea c++ style guide游
  • PxMatrix高性能RGB点阵驱动库技术解析
  • 乙巳马年春联生成终端生产环境部署:Nginx反向代理+HTTPS安全加固
  • ServoDAC:全软件定义的闭环RC数模转换器
  • VL53L0X ToF测距模块Arduino驱动库详解
  • leetcode No.76 | 滑动窗口与HashMap的应用
  • 微软研究院发现:让AI大模型变得更聪明却不消耗更多内存的方法
  • 从虚拟机到云服务器:我的Ansible学习环境搭建全记录(Ubuntu 22.04 + PPA源)
  • Claude Opus 4.6差评如潮!思考深度暴跌67%,AMD总监6852次日志打脸
  • 从零开始:将.NET 8 WebAPI无缝迁移至Docker的最佳实践
  • Qt入门基础
  • GxEPD2电子墨水驱动库深度解析:e-Paper嵌入式开发实战指南
  • 基于Python的智慧养老中心管理系统毕业设计
  • 模拟型对讲系统市场洞察:2026-2032年复合增长率(CAGR)为3.9%
  • ESP32驱动NIDEC 24H电机控制器全栈方案
  • LwRB:嵌入式轻量级环形缓冲区原理与DMA零拷贝实践
  • ZED视频录制全解析:从SVO格式到NVENC硬件加速
  • FreeRTOS任务优先级设错了?我的Zynq7020 TCP热拔插功能直接挂了
  • OPTIGA™ Trust M安全芯片Arduino开发全解析