FPGA零基础入门:Verilog语法攻略

🚀 FPGA零基础入门:Verilog语法全攻略

💡 前言

你是否曾经好奇过芯片是如何设计出来的?想要亲手"搭建"一个数字电路吗?

别担心,即使你完全没有硬件基础,这篇教程也会带你一步步走进FPGA的奇妙世界!


🎯 什么是FPGA?为什么要学Verilog?

FPGA简介

FPGA(Field Programmable Gate Array,现场可编程门阵列)是一种可以通过编程来实现各种数字电路功能的芯片。简单来说,它就像"数字电路界的乐高积木",你可以用代码来"搭建"出各种电路。

为什么选择Verilog?

FPGA开发主要有两种语言:VHDL和Verilog。其中:

  • Verilog(1995年成为IEEE标准)语法更接近C语言,学习曲线平缓
  • VHDL(1987年成为IEEE标准)语法较为严格,更接近Ada语言

Verilog是目前应用最广泛的一种硬件描述语言,如果你有C语言基础,那么学习Verilog会非常轻松!


🔄 Verilog与C语言:你的学习捷径

相似的控制结构

好消息!如果你学过C语言,你会发现Verilog的很多语法都很眼熟:

功能 C语言 Verilog
函数/模块 sub-function module, function, task
条件判断 if-then-else if-then-else
多分支选择 case case
代码块 { } begin ... end
循环 for, while for, while
宏定义 #define ``define`
整数类型 int integer

熟悉的运算符

几乎所有C语言的运算符在Verilog中都能找到对应:

verilog 复制代码
// 算术运算
a + b;  a - b;  a * b;  a / b;  a % b;

// 逻辑运算  
&&  ||  !

// 关系运算
>  <  >=  <=  ==  !=

// 位运算
&  |  ^  ~  <<  >>

// 三目运算符
condition ? true_value : false_value;

🏗️ Verilog基础语法:从零开始

1️⃣ 模块结构:Verilog的"主函数"

在C语言中,程序从main()函数开始。在Verilog中,**模块(module)**就是基本单元,就像搭积木的一个个部件。

基本结构
verilog 复制代码
module 模块名 (
    // 端口声明(输入输出接口)
    input wire a,
    input wire b,
    output wire c
);

    // 逻辑功能实现
    assign c = a & b;  // c = a AND b
    
endmodule
实际例子:一个简单的或门和与门
verilog 复制代码
module logic_gates (
    // 输入信号
    input wire a,
    input wire b,
    // 输出信号
    output wire or_result,   // 或门输出
    output wire and_result   // 与门输出
);

    // 逻辑实现
    assign or_result = a | b;   // 或运算
    assign and_result = a & b;  // 与运算
    
endmodule

2️⃣ 信号类型:电路中的"变量"

📌 输入输出信号
verilog 复制代码
input       // 输入端口
output      // 输出端口  
inout       // 双向端口(既能输入又能输出)

例子:

verilog 复制代码
input clk;              // 单bit输入
input [7:0] data_in;    // 8bit输入总线
output [3:0] led;       // 4bit输出
📌 内部信号类型

Verilog中有两种主要的信号类型,这是初学者最容易混淆的地方:

🔹 wire型 - "导线"

  • 代表物理连线
  • 用于组合逻辑(如assign语句)
  • 不能存储数值,只是传递信号
verilog 复制代码
wire connection;
assign connection = a & b;  // 组合逻辑,立即响应

🔹 reg型 - "寄存器"

  • 可以存储数值
  • 用在always块中
  • 虽然叫"reg",但不一定综合成寄存器
verilog 复制代码
reg counter;
always @(posedge clk) begin
    counter <= counter + 1;  // 时序逻辑,在时钟上升沿更新
end

⚠️ 新手易错点:
reg类型的名字容易误导人!在always块中被赋值的信号必须声明为reg,但实际综合后可能是组合逻辑电路,并非寄存器。


3️⃣ 数据表示:数字的"方言"

进制表示法

Verilog支持多种进制,格式为:<位宽>'<进制><数值>

verilog 复制代码
8'd100      // 8位十进制数100
8'b1010     // 8位二进制数1010  
8'hFF       // 8位十六进制数FF
8'o77       // 8位八进制数77

// 可以用下划线增加可读性
16'b1111_0000_1010_0101  // 等同于 16'hF0A5
特殊值
verilog 复制代码
x  // 不定值(don't care)
z  // 高阻态(未连接)
参数定义:硬件中的"常量"
verilog 复制代码
parameter WIDTH = 8;           // 可被外部修改
localparam STATE_IDLE = 2'b00; // 仅本模块使用

reg [WIDTH-1:0] data;  // 使用参数定义位宽

4️⃣ 赋值语句:阻塞 vs 非阻塞

这是Verilog最重要也最容易出错的概念!

🔸 阻塞赋值 = (类似C语言)
verilog 复制代码
always @(*) begin
    a = b;
    c = a;  // c的值是b的值,按顺序执行
end

特点:

  • 顺序执行
  • 用于组合逻辑
  • 像C语言一样"立即生效"
🔸 非阻塞赋值 <= (硬件特有)
verilog 复制代码
always @(posedge clk) begin
    a <= b;
    c <= a;  // c的值是a的旧值,并行执行
end

特点:

  • 并行执行
  • 用于时序逻辑
  • 所有赋值同时发生(在时钟沿)

💡 记忆口诀:
组合用等号,时序用小于等于!


5️⃣ 条件语句:硬件中的"决策"

if-else语句
verilog 复制代码
// 简单if
if (a > b)
    out <= a;

// if-else
if (a > b)
    out <= a;
else
    out <= b;

// 多重if-else(优先级编码器)
if (sel == 2'b00)
    out <= data0;
else if (sel == 2'b01)
    out <= data1;
else if (sel == 2'b10)
    out <= data2;
else
    out <= data3;
case语句:多路选择器的最佳实践
verilog 复制代码
always @(*) begin
    case (sel)
        2'b00: out = data0;
        2'b01: out = data1;
        2'b10: out = data2;
        2'b11: out = data3;
        default: out = 8'h00;  // 务必加default!
    endcase
end

变体:

  • casez: 将z?视为"任意值"
  • casex: 将xz?都视为"任意值"

6️⃣ 循环语句:代码复用的艺术

for循环:最常用
verilog 复制代码
integer i;
always @(*) begin
    for (i = 0; i < 8; i = i + 1) begin
        result[i] = data[i] ^ parity;
    end
end

⚠️ 重要提示:

Verilog中的for循环会被展开成并行硬件,不是像C语言那样逐次执行!

while循环:条件重复
verilog 复制代码
while (count < 10) begin
    sum = sum + data[count];
    count = count + 1;
end
repeat循环:固定次数
verilog 复制代码
repeat (8) begin
    result = result + shift_data;
    shift_data = shift_data << 1;
end
forever循环:永久执行(仅用于仿真)
verilog 复制代码
initial begin
    forever #10 clk = ~clk;  // 每10个时间单位翻转时钟
end

7️⃣ 结构说明语句:程序的"骨架"

initial块:初始化(仅仿真)
verilog 复制代码
initial begin
    clk = 0;
    rst = 1;
    #100 rst = 0;  // 100时间单位后复位解除
end

📝 注意: initial块只在仿真中执行,不能综合成实际硬件!

always块:硬件行为描述

组合逻辑:

verilog 复制代码
always @(*) begin  // @(*)表示对所有输入敏感
    sum = a + b;
end

时序逻辑:

verilog 复制代码
always @(posedge clk or posedge rst) begin  // 时钟上升沿或复位
    if (rst)
        counter <= 0;
    else
        counter <= counter + 1;
end

8️⃣ 任务与函数:代码复用

function:返回单一值
verilog 复制代码
function [7:0] calc_parity;
    input [7:0] data;
    begin
        calc_parity = ^data;  // 计算奇偶校验位
    end
endfunction

// 调用
assign parity = calc_parity(input_data);

特点:

  • 必须有返回值
  • 至少有一个输入
  • 执行时间为0(组合逻辑)
task:执行复杂操作
verilog 复制代码
task write_data;
    input [7:0] addr;
    input [7:0] data;
    begin
        @(posedge clk);
        address <= addr;
        write_en <= 1;
        data_out <= data;
        @(posedge clk);
        write_en <= 0;
    end
endtask

// 调用
write_data(8'h10, 8'hAB);

特点:

  • 可以有多个输入输出
  • 可以消耗仿真时间
  • 可以调用其他task

🛠️ 系统任务:调试的利器

打印输出

verilog 复制代码
$display("Counter = %d, Time = %t", counter, $time);  // 带换行
$write("Data = %h", data);                            // 不换行

// 格式控制符
%d  // 十进制
%h  // 十六进制
%b  // 二进制
%t  // 时间格式

监控信号

verilog 复制代码
initial begin
    $monitor($time, " clk=%b data=%h", clk, data);
    // 每当clk或data变化时自动打印
end

结束仿真

verilog 复制代码
$finish;  // 结束仿真

文件操作

verilog 复制代码
$readmemh("data.hex", memory);  // 从文件读取十六进制数据
$readmemb("data.bin", memory);  // 从文件读取二进制数据

🎓 编译预处理:高级技巧

宏定义

verilog 复制代码
`define WIDTH 8
`define MAX_COUNT 255

reg [`WIDTH-1:0] data;

文件包含

verilog 复制代码
`include "definitions.v"

时间尺度

verilog 复制代码
`timescale 1ns/1ps  // 时间单位/时间精度

条件编译

verilog 复制代码
`ifdef SIMULATION
    // 仅在仿真时编译
    initial $display("Simulation mode");
`else
    // 综合时编译
`endif

✨ 完整实例:8位计数器

让我们用一个完整的例子把所有知识串起来:

verilog 复制代码
`timescale 1ns/1ps

module counter_8bit (
    input wire clk,           // 时钟信号
    input wire rst_n,         // 低电平复位
    input wire en,            // 使能信号
    output reg [7:0] count,   // 计数值
    output wire overflow      // 溢出标志
);

    // 参数定义
    parameter MAX_COUNT = 255;
    
    // 组合逻辑:溢出检测
    assign overflow = (count == MAX_COUNT) ? 1'b1 : 1'b0;
    
    // 时序逻辑:计数器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            count <= 8'd0;           // 复位
        else if (en) begin
            if (count == MAX_COUNT)
                count <= 8'd0;       // 溢出归零
            else
                count <= count + 1;  // 递增
        end
    end
    
endmodule

测试模块(testbench):

verilog 复制代码
module tb_counter;
    reg clk, rst_n, en;
    wire [7:0] count;
    wire overflow;
    
    // 实例化被测模块
    counter_8bit uut (
        .clk(clk),
        .rst_n(rst_n),
        .en(en),
        .count(count),
        .overflow(overflow)
    );
    
    // 生成时钟
    initial begin
        clk = 0;
        forever #5 clk = ~clk;  // 10ns周期
    end
    
    // 测试激励
    initial begin
        rst_n = 0;
        en = 0;
        #20 rst_n = 1;
        #10 en = 1;
        #3000 $finish;
    end
    
    // 监控输出
    initial begin
        $monitor("Time=%0t count=%d overflow=%b", $time, count, overflow);
    end
    
endmodule

🎯 新手常见错误与避坑指南

❌ 错误1:混用阻塞和非阻塞赋值

verilog 复制代码
// 错误示范
always @(posedge clk) begin
    a = b;      // 阻塞赋值
    c <= a;     // 非阻塞赋值,混用!
end

// 正确写法
always @(posedge clk) begin
    a <= b;
    c <= a;     // 全部使用非阻塞
end

❌ 错误2:敏感列表不完整

verilog 复制代码
// 错误示范
always @(a) begin
    out = a + b;  // b没有在敏感列表中!
end

// 正确写法
always @(*) begin    // 或 always @(a, b)
    out = a + b;
end

❌ 错误3:case语句缺少default

verilog 复制代码
// 错误示范
always @(*) begin
    case (sel)
        2'b00: out = a;
        2'b01: out = b;
        // 缺少其他情况!可能产生锁存器
    endcase
end

// 正确写法
always @(*) begin
    case (sel)
        2'b00: out = a;
        2'b01: out = b;
        default: out = 8'h00;
    endcase
end

📚 学习路线建议

  1. 第一周: 掌握基本语法、数据类型、模块结构
  2. 第二周: 理解组合逻辑与时序逻辑的区别
  3. 第三周: 学习状态机设计(FSM)
  4. 第四周: 实践小项目(LED流水灯、按键消抖、串口通信等)

🔗 推荐学习资源

  • 在线仿真工具: EDA Playground (免费在线Verilog仿真)
  • 开发板推荐: FPGA入门开发板(如Basys3、DE10-Lite)
  • 经典书籍: 《Verilog HDL数字设计与综合》

📖 学习声明

本文是学习知识星球**「FPGA从入门到精通」**后按个人理解整理的学习笔记,内容可能存在理解不够深入或不够完善之处。

如果你希望获取更系统、更专业的FPGA与数字电路知识,建议:

  • 参考《Verilog经典教程》等权威书籍
  • 前往CSDN、知识星球、知乎等平台查阅更多高质量文章
  • 加入FPGA学习社区与同行交流

笔记整理有限,原创内容无限 🌟


💬 写在最后

FPGA的世界浩瀚无垠,Verilog只是你的第一把钥匙。希望这篇教程能帮你打开数字电路设计的大门,祝学习顺利!有问题欢迎交流讨论~

⭐ 如果觉得有帮助,别忘了点赞收藏哦!

相关推荐
受之以蒙2 小时前
Rust ndarray 高性能计算:从元素操作到矩阵运算的优化实践
人工智能·笔记·rust
霜绛2 小时前
Unity:lua热更新(一)——AB包AssetBundle、Lua语法
笔记·学习·游戏·unity·lua
霜绛2 小时前
Unity:lua热更新(二)——Lua语法(续)
笔记·学习·unity·游戏引擎·lua
YuforiaCode4 小时前
黑马Python+AI大模型开发课程笔记(个人记录、仅供参考)
笔记
lkbhua莱克瓦244 小时前
Java练习——数组练习
java·开发语言·笔记·github·学习方法
探索宇宙真理.4 小时前
OpenWebui 富文本提示词 远程命令注入漏洞 | CVE-2025-64495 复现&研究
经验分享·安全漏洞
AA陈超4 小时前
ASC学习笔记0010:效果被应用时的委托
c++·笔记·学习
AA陈超5 小时前
ASC学习笔记0004:通知相关方能力规格已被修改
c++·笔记·学习·游戏·ue5·游戏引擎·虚幻
hadage2335 小时前
--- git 笔记 ---
笔记·git·elasticsearch