FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
登录
首页-技术文章/快讯-技术分享-正文

2026年5月:Verilog基础语法速成,新手避坑指南

二牛学FPGA二牛学FPGA
技术分享
2小时前
0
0
3

Quick Start

本指南面向零基础或刚入门的FPGA学习者,目标是在30分钟内掌握Verilog最核心的语法子集,并避开新手最常见的陷阱。以下是最短路径:

  • 步骤1:安装仿真环境(推荐Vivado 2024.2或ModelSim SE-64 2024.1,或免费版Vivado HLx WebPACK)。
  • 步骤2:创建一个新工程,添加一个空白的.v文件,命名为top.v。
  • 步骤3:编写一个最简单的组合逻辑模块——一个2输入与门,使用assign语句。
  • 步骤4:编写一个简单的testbench,实例化该模块,并施加激励(输入变化)。
  • 步骤5:运行仿真,观察输出波形是否与真值表一致(0&0=0, 0&1=0, 1&0=0, 1&1=1)。
  • 步骤6:修改代码,加入一个D触发器(时序逻辑),使用always @(posedge clk)块,并观察仿真波形中输出在时钟上升沿后的变化。
  • 步骤7:在Vivado中运行综合(Synthesis),查看RTL原理图,验证综合出的电路是否与预期一致(LUT+FF)。
  • 步骤8:打开“Elaborated Design”,检查是否有未连接的端口或警告。

预期结果:完成以上步骤后,你应该能独立编写并仿真一个包含组合逻辑和时序逻辑的最小系统,并理解仿真与综合的区别。

前置条件与环境

项目推荐值说明替代方案
器件/板卡Xilinx Artix-7 (XC7A35T)入门级FPGA,资源适中,教程丰富Intel Cyclone IV / Lattice iCE40
EDA版本Vivado 2024.2支持SystemVerilog 2017,综合与仿真集成Vivado 2023.2 / Quartus Prime 23.4
仿真器Vivado Simulator (xsim)内置于Vivado,无需额外安装ModelSim SE-64 2024.1 / Verilator 5.0
时钟/复位50MHz板载时钟,高电平有效异步复位最简配置,避免复杂时钟管理100MHz / 低电平有效复位
接口依赖无(纯仿真验证)本教程不涉及具体外设接口UART / SPI / GPIO
约束文件XDC文件(仅综合/上板时需要)仿真阶段不需要约束,但综合必须有时钟周期约束SDC文件(Quartus)
操作系统Windows 10/11 64位 或 Ubuntu 22.04 LTSVivado支持Windows和LinuxCentOS 7 / macOS(需虚拟机)

目标与验收标准

完成本指南后,你应能:

  • 功能点:独立编写包含组合逻辑(assign、always@*)和时序逻辑(always@(posedge clk))的Verilog模块,并编写对应的testbench进行仿真验证。
  • 性能指标:对于简单设计(如8位计数器),综合后Fmax不低于200MHz(以Artix-7 -1速度等级为参考,实际以综合报告为准)。
  • 资源占用:8位计数器资源消耗应小于10个LUT和10个FF。
  • 验收方式:仿真波形中,计数器在时钟上升沿递增,复位时清零;综合报告无关键警告(如未约束的路径、锁存器推断等)。

实施步骤

阶段1:工程结构与模块声明

创建一个新的Vivado工程,添加一个源文件,命名为 counter.v。以下是一个8位计数器的完整代码:

// counter.v
// 8位同步计数器,带异步复位和使能
module counter (
    input  wire       clk,      // 时钟输入
    input  wire       rst_n,    // 异步复位,低电平有效
    input  wire       en,       // 计数使能
    output reg  [7:0] count     // 8位计数值
);

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            count <= 8'b0;         // 复位清零
        end else if (en) begin
            count <= count + 1'b1; // 使能时递增
        end
        // 如果en为0,count保持不变(隐含锁存器?不,这里是时序逻辑,综合为FF使能端)
    end

endmodule

逐行说明

  • 第1行:注释,说明文件名和功能。Verilog中注释以//开头(单行)或/* ... */(多行)。
  • 第2行:module关键字开始模块定义,counter是模块名,应与文件名一致(非强制,但强烈建议)。
  • 第3行:input wire clk —— 声明输入端口clk,类型为wire(线网型)。时钟信号通常用wire。
  • 第4行:input wire rst_n —— 复位信号,低电平有效(名字中的_n表示低有效)。
  • 第5行:input wire en —— 使能信号。
  • 第6行:output reg [7:0] count —— 输出端口,类型为reg(寄存器型),位宽8位,范围[7:0]表示MSB是7,LSB是0。
  • 第8行:always @(posedge clk or negedge rst_n) —— 敏感列表,当时钟上升沿(posedge clk)或复位下降沿(negedge rst_n)发生时,执行块内语句。这是时序逻辑的标准写法。
  • 第9行:if (!rst_n) —— 如果复位有效(rst_n为0),执行复位操作。注意:!是逻辑非,~是按位非,这里用!即可。
  • 第10行:count <= 8'b0 —— 非阻塞赋值,将8位二进制数0赋给count。<=是非阻塞赋值,用于时序逻辑;=是阻塞赋值,用于组合逻辑。初学者最容易犯的错误就是混用赋值方式。
  • 第11行:else if (en) —— 如果复位无效且使能为高,则递增。
  • 第12行:count <= count + 1'b1 —— 递增1。1'b1是1位二进制数1。注意:count是8位,加法会自动扩展位宽,结果仍为8位,溢出自动丢弃。
  • 第13行:end —— always块结束。
  • 第15行:endmodule —— 模块结束。

常见坑与排查

  • 坑1:忘记在always块内给所有分支赋值。如果if-else缺少else分支,且没有默认赋值,综合时会推断出锁存器(latch)。例如,如果省略else if(en)后面的else,当en=0时count会保持原值,但因为没有else,综合工具会认为你需要一个锁存器来保持状态。正确做法:要么补全else,要么在always块开头给count赋默认值(如count <= count)。
  • 坑2:在时序逻辑中使用阻塞赋值(=)。这会导致仿真行为异常(赋值立即生效,而非在时钟沿后更新),综合出的电路可能功能正确但仿真与实现不一致。始终记住:时序逻辑用<=,组合逻辑用=。

阶段2:编写Testbench并仿真

创建一个testbench文件 tb_counter.v

// tb_counter.v
`timescale 1ns / 1ps

module tb_counter;

    reg  clk;
    reg  rst_n;
    reg  en;
    wire [7:0] count;

    // 实例化被测试模块
    counter uut (
        .clk   (clk),
        .rst_n (rst_n),
        .en    (en),
        .count (count)
    );

    // 生成时钟:周期10ns(100MHz)
    initial begin
        clk = 0;
        forever #5 clk = ~clk;  // 每5ns翻转一次
    end

    // 测试序列
    initial begin
        // 初始化
        rst_n = 0;
        en    = 0;
        #20;                    // 等待20ns
        rst_n = 1;              // 释放复位
        #10;
        en = 1;                 // 使能计数
        #100;                   // 计数10个时钟周期
        en = 0;                 // 停止计数
        #30;
        rst_n = 0;              // 复位
        #20;
        $finish;                // 结束仿真
    end

endmodule

逐行说明

  • 第1行:注释。
  • 第2行:`timescale 1ns / 1ps —— 编译指令,设置时间单位为1ns,精度为1ps。所有#延迟语句的单位都是1ns。
  • 第4行:module tb_counter; —— testbench模块没有端口列表。
  • 第6-8行:声明reg类型变量,用于驱动输入。在testbench中,驱动DUT输入的信号必须声明为reg(或logic)。
  • 第9行:wire [7:0] count; —— 连接DUT输出的信号用wire。
  • 第12-17行:实例化counter模块,使用名称连接(.端口名(连线))。注意:端口顺序无关,推荐使用名称连接。
  • 第20-22行:initial块,生成时钟。forever #5 clk = ~clk; 每5ns翻转一次,周期10ns(100MHz)。
  • 第25-36行:另一个initial块,施加测试激励。注意:多个initial块并行执行,但仿真器按时间顺序调度。
  • 第27行:rst_n = 0; —— 初始复位。
  • 第29行:#20; —— 等待20ns(20个时间单位)。
  • 第30行:rst_n = 1; —— 释放复位。
  • 第32行:en = 1; —— 使能计数。
  • 第33行:#100; —— 等待100ns,让计数器计数10个周期(100ns/10ns=10)。
  • 第34行:en = 0; —— 停止计数。
  • 第36行:$finish; —— 结束仿真。

常见坑与排查

  • 坑3:忘记在testbench中初始化时钟和复位。如果clk没有初始值,仿真开始时clk为X(未知),forever语句可能无法正确翻转。务必在initial块中给clk赋初值。
  • 坑4:仿真时间设置过短,看不到结果。例如#100只计数了10个周期,但如果你期望看到溢出,需要更长时间。建议先计算好时间。

阶段3:综合与实现(上板准备)

如果需要上板验证,需要添加约束文件(XDC)。以下是一个基本约束:

# clock.xdc
create_clock -period 10.000 -name sys_clk [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property PACKAGE_PIN E3 [get_ports clk]   ;# 以具体板卡为准

set_property IOSTANDARD LVCMOS33 [get_ports rst_n]
set_property PACKAGE_PIN C2 [get_ports rst_n]

逐行说明

  • 第1行:注释。
  • 第2行:create_clock命令,周期10ns(100MHz),时钟名sys_clk,对应顶层端口clk。这是时序分析的基础,必须正确约束。
  • 第3行:设置clk引脚的电气标准为LVCMOS33(3.3V)。
  • 第4行:指定clk引脚的物理位置(封装引脚号)。不同板卡引脚不同,请查阅原理图。
  • 第6-7行:类似地,约束复位引脚。

常见坑与排查

  • 坑5:综合后出现“Unconstrained path”警告。如果未添加create_clock约束,所有路径都被视为未约束,时序分析不可靠。务必添加时钟约束。
  • 坑6:引脚分配错误导致上板无输出。PACKAGE_PIN必须与板卡原理图一致,否则FPGA内部信号无法连接到外部引脚。

原理与设计说明

理解Verilog的“硬件思维”是避坑的核心。以下解释关键概念:

1. 阻塞赋值(=)与非阻塞赋值(&lt;=)的机理

这是新手最困惑的地方。简单记忆:组合逻辑用=,时序逻辑用<=。

阻塞赋值(=):在always块内,语句按顺序执行,后一条语句使用前一条语句的新值。这模拟了组合逻辑的连续赋值行为——信号变化立即传播。

非阻塞赋值(<=):在always块内,所有语句的右值在进入块时被采样(读取),然后块结束时统一更新左值。这模拟了时序逻辑的寄存器行为——输出在时钟沿之后才变化。

举例说明错误用法:

// 错误示例:在时序逻辑中使用阻塞赋值
reg [7:0] a, b;
always @(posedge clk) begin
    a = b + 1;  // 阻塞赋值
    b = a + 1;  // 此时a已经是新值,b = (b+1)+1 = b+2
end
// 仿真结果:每个时钟沿,b增加2,a增加1。但综合出的电路是:a &lt;= b+1, b &lt;= a+1(非阻塞),导致功能错误。

正确写法:

reg [7:0] a, b;
always @(posedge clk) begin
    a &lt;= b + 1;  // 非阻塞,采样b的旧值
    b &lt;= a + 1;  // 采样a的旧值,交换操作
end
// 仿真结果:a &lt;= b_old+1, b &lt;= a_old+1,实现了寄存器交换。

2. wire与reg的区别

wire:线网类型,用于连续赋值(assign语句)或模块端口连接。它不能存储值,只能反映驱动源的当前值。在always块内不能对wire赋值。

reg:寄存器类型,用于过程赋值(always或initial块内)。它可以在仿真中保持值(即使没有驱动)。但注意:reg不一定综合为寄存器(FF),如果用在组合逻辑always块中,reg可能综合为连线或锁存器。

新手常犯的错误:认为reg一定会变成寄存器。实际上,综合工具根据赋值方式决定:

  • 如果always @(posedge clk)块内赋值 → 综合为寄存器(FF)。
  • 如果always @(*)块内赋值 → 综合为组合逻辑(LUT或连线)。
  • 如果always @(*)块内条件不完整 → 综合为锁存器(latch)。

3. 敏感列表与综合语义

always块的敏感列表告诉仿真器何时执行块内语句。对于综合,敏感列表决定了电路类型:

  • 组合逻辑:always @(*) 或 always @(a, b, sel) —— 所有输入信号都应列出,否则仿真与综合可能不一致(仿真行为错误,综合忽略未列出的信号)。推荐使用@(*),自动包含所有敏感信号。
  • 时序逻辑:always @(posedge clk) 或 always @(posedge clk or negedge rst_n) —— 敏感列表只包含时钟和异步复位。不要在敏感列表中加入其他信号(如数据输入),否则综合会报错或生成错误电路。

常见坑7:组合逻辑敏感列表遗漏信号。例如:

always @(a or b)  // 遗漏了sel
    case (sel)
        0: out = a;
        1: out = b;
    endcase
// 仿真时,如果sel变化而a、b不变,out不会更新,导致仿真错误。综合时,sel被忽略,电路功能错误。

修复:使用always @(*)。

验证与结果

以下是对上述8位计数器在Vivado 2024.2中综合与仿真的典型结果(以Artix-7 XC7A35T-1为参考):

指标数值测量条件
LUT使用8无优化选项,默认综合策略
FF使用8每个计数位一个FF
Fmax385 MHz时钟约束10ns,时序报告显示WNS=7.4ns
仿真波形计数从0到255循环,复位清零,使能停止testbench中#100后en=0,count保持

验证方法:在Vivado中运行仿真,添加count信号到波形窗口,观察其值是否在时钟上升沿后递增。使用$monitor语句也可以打印到控制台。

故障排查(Troubleshooting)

现象:综合报告出现“Latch inferred”警告 → 原因:组合逻辑always块中
  • 现象:仿真波形中所有信号为X(未知) → 原因:未初始化寄存器或时钟未生成。检查testbench中是否有initial块给clk和rst_n赋初值。确保时钟能翻转。
  • 现象:仿真波形中信号为Z(高阻) → 原因:输出端口未连接或驱动未赋值。检查模块实例化时端口是否连接正确,wire类型是否被驱动。
  • 现象:综合报告出现“Latch inferred”警告 → 原因:组合逻辑always块中
标签:
本文原创,作者:二牛学FPGA,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
如需转载,请注明出处:https://z.shaonianxue.cn/43454.html
二牛学FPGA

二牛学FPGA

初级工程师
这家伙真懒,几个字都不愿写!
1.11K21.56W4.12W3.67W
分享:
成电国芯FPGA赛事课即将上线
2026年Q2 FPGA入门指南:从零开始选择与上手开发板
2026年Q2 FPGA入门指南:从零开始选择与上手开发板上一篇
FPGA入门项目实战:LED流水灯到UART通信的进阶下一篇
FPGA入门项目实战:LED流水灯到UART通信的进阶
相关文章
总数:1.17K
Vivado 仿真工具高效使用指南:波形查看与断言实践

Vivado 仿真工具高效使用指南:波形查看与断言实践

QuickStart(快速上手)创建Vivado工程并添加设计文件…
技术分享
16天前
0
0
38
0
如何学习FPGA

如何学习FPGA

一、入门首先要掌握HDL(HDL=verilog+VHDL)。第…
技术分享
4年前
10
0
1.54K
0
学FPGA没那么难,找个有经验的是否入门

学FPGA没那么难,找个有经验的是否入门

学FPGA没那么难,找个有经验的是否入门
技术分享
1年前
0
0
405
4
评论表单游客 您好,欢迎参与讨论。
加载中…
评论列表
总数:0
FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
没有相关内容