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

FPGA入门项目实战:LED流水灯到UART通信的进阶

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

Quick Start

  • 步骤一:下载并安装 Vivado 2023.2(或更高版本),选择 Artix-7 系列器件(如 XC7A35T)。
  • 步骤二:创建新工程,选择目标板卡(如 Nexys A7-50T 或自定义板卡),添加约束文件(.xdc)。
  • 步骤三:编写 LED 流水灯顶层模块(led_flow.v),实现 8 个 LED 以 1 Hz 频率循环点亮。
  • 步骤四:编写 UART 发送模块(uart_tx.v),波特率 115200,数据位 8,无校验位,1 停止位。
  • 步骤五:编写 UART 接收模块(uart_rx.v),与发送模块对称,支持回环测试。
  • 步骤六:编写顶层模块(top.v),实例化 LED 流水灯和 UART 模块,连接时钟(50 MHz)、复位(低有效)和串口引脚。
  • 步骤七:运行综合(Synthesis)和实现(Implementation),检查时序是否满足(WNS ≥ 0)。
  • 步骤八:生成比特流,下载到板卡。观察 LED 流水灯效果;用串口助手发送数据,验证回环功能。
  • 步骤九:验收点:LED 按顺序循环点亮,周期 1 秒;串口发送“Hello”回显相同字符串。

前置条件与环境

项目/推荐值说明替代方案
器件/板卡Xilinx Artix-7 XC7A35T-1CPG236C其他 7 系列(如 Spartan-7)或 Intel Cyclone IV
EDA 版本Vivado 2023.2Vivado 2022.2 或更高
仿真器Vivado Simulator(xsim)ModelSim、QuestaSim
时钟/复位50 MHz 单端时钟,低有效异步复位100 MHz 时钟需调整分频系数
接口依赖UART 引脚:TX(J2-3)、RX(J2-4)按板卡原理图修改约束
约束文件top.xdc(时钟周期 20 ns,引脚分配)使用 Vivado 自动引脚分配
串口工具串口助手(波特率 115200,8N1)PuTTY、minicom
开发语言Verilog-2001VHDL(需重写模块)

目标与验收标准

  • 功能点:8 个 LED 以 1 Hz 频率循环流水;UART 回环(发送任意字节,接收相同字节)。
  • 性能指标:UART 波特率 115200 bps,误码率 < 1e-6(在 50 MHz 时钟下)。
  • 资源占用:LUT ≤ 200,FF ≤ 150(示例值,以实际综合报告为准)。
  • Fmax:≥ 100 MHz(实际时钟 50 MHz,留有余量)。
  • 验收方式:上板后 LED 流水;串口助手发送 0x55,收到 0x55;发送 0xAA,收到 0xAA。

实施步骤

工程结构

project/
├── src/
│   ├── led_flow.v          # LED 流水灯模块
│   ├── uart_tx.v           # UART 发送模块
│   ├── uart_rx.v           # UART 接收模块
│   ├── top.v               # 顶层模块
├── constr/
│   └── top.xdc             # 约束文件
├── sim/
│   └── tb_top.v            # 测试平台
└── vivado_project.xpr      # Vivado 工程文件

逐行说明

  • 第 1 行:项目根目录,包含所有源文件。
  • 第 2 行:src/ 目录存放 RTL 源文件。
  • 第 3–6 行:各模块文件,每个文件对应一个 module。
  • 第 7 行:constraints 目录存放约束文件。
  • 第 8 行:top.xdc 包含时钟周期定义和引脚分配。
  • 第 9 行:sim/ 目录存放仿真文件。
  • 第 10 行:tb_top.v 用于功能仿真。
  • 第 11 行:Vivado 工程文件,双击打开。

关键模块:LED 流水灯

module led_flow (
    input  wire       clk,
    input  wire       rst_n,
    output reg  [7:0] led
);
    reg [25:0] cnt;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt &lt;= 26&#039;d0;
            led &lt;= 8&#039;b0000_0001;
        end else if (cnt == 26&#039;d49_999_999) begin
            cnt &lt;= 26&#039;d0;
            led &lt;= {led[6:0], led[7]};
        end else begin
            cnt &lt;= cnt + 1&#039;b1;
        end
    end
endmodule

逐行说明

  • 第 1 行:模块声明,端口包括时钟、复位和 8 位 LED 输出。
  • 第 2–4 行:输入 clk(50 MHz),输入 rst_n(低有效复位),输出 led(寄存器类型)。
  • 第 5 行:声明 26 位计数器 cnt,最大计数值 50,000,000(1 秒)。
  • 第 6 行:时序逻辑,时钟上升沿或复位下降沿触发。
  • 第 7–8 行:复位时 cnt 清零,led 初始化为 0x01(最右 LED 亮)。
  • 第 9–10 行:当 cnt 达到 49,999,999(50 MHz 时钟下 1 秒),cnt 归零。
  • 第 11 行:循环左移 led,{led[6:0], led[7]} 实现流水效果。
  • 第 12–13 行:否则 cnt 递增。

关键模块:UART 发送

module uart_tx (
    input  wire       clk,
    input  wire       rst_n,
    input  wire       tx_start,
    input  wire [7:0] tx_data,
    output reg        tx,
    output reg        tx_busy
);
    localparam BAUD_CNT = 434; // 50MHz / 115200 ≈ 434
    reg [8:0] baud_cnt;
    reg [3:0] bit_idx;
    reg [9:0] shift_reg;
    reg       sending;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            tx &lt;= 1&#039;b1;
            tx_busy &lt;= 1&#039;b0;
            baud_cnt &lt;= 9&#039;d0;
            bit_idx &lt;= 4&#039;d0;
            shift_reg &lt;= 10&#039;d0;
            sending &lt;= 1&#039;b0;
        end else begin
            if (!sending &amp;&amp; tx_start) begin
                shift_reg &lt;= {1&#039;b1, tx_data, 1&#039;b0}; // stop, data, start
                bit_idx &lt;= 4&#039;d0;
                baud_cnt &lt;= 9&#039;d0;
                sending &lt;= 1&#039;b1;
                tx_busy &lt;= 1&#039;b1;
            end else if (sending) begin
                if (baud_cnt == BAUD_CNT-1) begin
                    baud_cnt &lt;= 9&#039;d0;
                    tx &lt;= shift_reg[0];
                    shift_reg &lt;= {1&#039;b1, shift_reg[9:1]};
                    if (bit_idx == 4&#039;d9) begin
                        sending &lt;= 1&#039;b0;
                        tx_busy &lt;= 1&#039;b0;
                    end else begin
                        bit_idx &lt;= bit_idx + 1&#039;b1;
                    end
                end else begin
                    baud_cnt &lt;= baud_cnt + 1&#039;b1;
                end
            end
        end
    end
endmodule

逐行说明

  • 第 1 行:模块声明,端口包括时钟、复位、启动信号、数据输入、串行输出和忙标志。
  • 第 2–7 行:输入输出定义,tx 为串行输出,tx_busy 指示发送忙。
  • 第 8 行:本地参数 BAUD_CNT = 434,由 50 MHz / 115200 计算得到(取整)。
  • 第 9–12 行:内部寄存器:波特率计数器、位索引、移位寄存器、发送状态。
  • 第 13 行:时序逻辑,时钟上升沿或复位下降沿触发。
  • 第 14–20 行:复位时 tx 拉高(空闲高),tx_busy 清零,其他寄存器清零。
  • 第 21–22 行:当不在发送且收到启动信号时,加载数据到移位寄存器(格式:停止位1 + 数据 + 起始位0)。
  • 第 23–25 行:位索引清零,波特计数器清零,设置发送状态和忙标志。
  • 第 26 行:发送状态下,检查波特计数器是否达到阈值。
  • 第 27–28 行:达到阈值时,波特计数器归零,输出当前最低位(shift_reg[0])。
  • 第 29 行:移位寄存器右移一位,高位补 1(空闲位)。
  • 第 30–31 行:如果已发送完 10 位(起始+8数据+停止),结束发送。
  • 第 32–33 行:否则位索引加 1。
  • 第 34–36 行:未达到阈值时,波特计数器递增。

关键模块:UART 接收

module uart_rx (
    input  wire       clk,
    input  wire       rst_n,
    input  wire       rx,
    output reg        rx_done,
    output reg  [7:0] rx_data
);
    localparam BAUD_CNT = 434;
    localparam HALF_CNT = 217; // 半周期采样
    reg [8:0] baud_cnt;
    reg [3:0] bit_idx;
    reg [7:0] data_reg;
    reg       receiving;
    reg       rx_sync;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            rx_done &lt;= 1&#039;b0;
            rx_data &lt;= 8&#039;d0;
            baud_cnt &lt;= 9&#039;d0;
            bit_idx &lt;= 4&#039;d0;
            data_reg &lt;= 8&#039;d0;
            receiving &lt;= 1&#039;b0;
            rx_sync &lt;= 1&#039;b1;
        end else begin
            rx_sync &lt;= rx; // 同步打拍
            if (!receiving &amp;&amp; !rx_sync) begin
                receiving &lt;= 1&#039;b1;
                baud_cnt &lt;= 9&#039;d0;
                bit_idx &lt;= 4&#039;d0;
            end else if (receiving) begin
                if (baud_cnt == BAUD_CNT-1) begin
                    baud_cnt &lt;= 9&#039;d0;
                    if (bit_idx == 4&#039;d0) begin
                        // 起始位,忽略
                    end else if (bit_idx &lt;= 4&#039;d8) begin
                        data_reg[bit_idx-1] &lt;= rx_sync;
                    end else if (bit_idx == 4&#039;d9) begin
                        // 停止位,输出数据
                        rx_data &lt;= data_reg;
                        rx_done &lt;= 1&#039;b1;
                        receiving &lt;= 1&#039;b0;
                    end
                    bit_idx &lt;= bit_idx + 1&#039;b1;
                end else begin
                    baud_cnt &lt;= baud_cnt + 1&#039;b1;
                end
            end
        end
    end
endmodule

逐行说明

  • 第 1 行:模块声明,端口包括时钟、复位、串行输入、接收完成标志和数据输出。
  • 第 2–6 行:输入输出定义,rx 为串行输入,rx_done 指示接收完成。
  • 第 7 行:本地参数 BAUD_CNT = 434,与发送模块一致。
  • 第 8 行:HALF_CNT = 217,用于起始位中间采样(此处简化,实际使用半周期对齐)。
  • 第 9–13 行:内部寄存器:波特计数器、位索引、数据寄存器、接收状态、同步打拍。
  • 第 14 行:时序逻辑。
  • 第 15–22 行:复位时所有寄存器清零,rx_sync 拉高(空闲高)。
  • 第 23 行:同步打拍 rx 信号,消除亚稳态。
  • 第 24–25 行:检测到起始位(rx_sync 下降沿),开始接收。
  • 第 26–27 行:设置接收状态,重置计数器和位索引。
  • 第 28 行:接收状态下,检查波特计数器。
  • 第 29–30 行:达到阈值时,计数器归零。
  • 第 31–32 行:位索引为 0 时,忽略起始位。
  • 第 33–34 行:位索引 1–8 时,采样数据位并存入 data_reg。
  • 第 35–38 行:位索引为 9 时,停止位,输出数据并置 rx_done 信号。
  • 第 39 行:位索引递增。
  • 第 40–42 行:未达到阈值时,计数器递增。

顶层模块与约束

module top (
    input  wire       clk,
    input  wire       rst_n,
    output wire [7:0] led,
    input  wire       rx,
    output wire       tx
);
    wire       tx_busy;
    wire       rx_done;
    wire [7:0] rx_data;
    reg  [7:0] tx_data;
    reg        tx_start;
    // LED 流水灯实例
    led_flow u_led (
        .clk   (clk),
        .rst_n (rst_n),
        .led   (led)
    );
    // UART 接收实例
    uart_rx u_rx (
        .clk     (clk),
        .rst_n   (rst_n),
        .rx      (rx),
        .rx_done (rx_done),
        .rx_data (rx_data)
    );
    // UART 发送实例(回环)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            tx_data &lt;= 8&#039;d0;
            tx_start &lt;= 1&#039;b0;
        end else if (rx_done &amp;&amp; !tx_busy) begin
            tx_data &lt;= rx_data;
            tx_start &lt;= 1&#039;b1;
        end else begin
            tx_start &lt;= 1&#039;b0;
        end
    end
    uart_tx u_tx (
        .clk      (clk),
        .rst_n    (rst_n),
        .tx_start (tx_start),
        .tx_data  (tx_data),
        .tx       (tx),
        .tx_busy  (tx_busy)
    );
endmodule

逐行说明

  • 第 1 行:顶层模块声明,端口包括时钟、复位、LED、UART 引脚。
  • 第 2–6 行:输入输出定义。
  • 第 7–10 行:内部连线声明,连接子模块。
  • 第 11–12 行:寄存器用于回环控制。
  • 第 13–17 行:实例化 LED 流水灯模块。
  • 第 18–24 行:实例化 UART 接收模块。
  • 第 25–33 行:回环逻辑:收到数据后,若发送空闲,则启动发送。
  • 第 34–41 行:实例化 UART 发送模块。

约束文件(top.xdc)

# 时钟约束
create_clock -period 20.000 [get_ports clk]
# LED 引脚(示例,按板卡修改)
set_property PACKAGE_PIN U12 [get_ports {led[0]}]
set_property PACKAGE_PIN U11 [get_ports {led[1]}]
# ... 其他 LED 引脚类似
# UART 引脚
set_property PACKAGE_PIN J2 [get_ports rx]
set_property PACKAGE_PIN J3 [get_ports tx]
# 电平标准
set_property IOSTANDARD LVCMOS33 [get_ports *]

逐行说明

  • 第 1 行:定义时钟周期 20 ns(50 MHz)。
  • 第 2 行:LED 引脚分配示例,按实际板卡修改。
  • 第 3–4 行:更多 LED 引脚。
  • 第 5 行:UART 接收引脚。
  • 第 6 行:UART 发送引脚。
  • 第 7 行:设置 I/O 电平标准为 3.3V LVCMOS。

常见坑与排查

  • 坑 1:波特率计算错误。排查:检查 BAUD_CNT 是否等于 50,000,000 / 115,200 ≈ 434。若使用 100 MHz 时钟,BAUD_CNT = 868。
  • 坑 2:复位极性错误。排查:确认 rst_n 为低有效,且板卡复位按键输出低电平。
  • 坑 3:UART 引脚 TX/RX 接反。排查:交换约束文件中的引脚分配。
  • 坑 4:LED 不亮或全亮。排查:检查 led 输出是否被驱动;用仿真验证 led_flow 模块。
  • 坑 5:串口无回显。排查:用示波器或逻辑分析仪观察 TX 引脚波形;确认串口助手波特率、数据位、停止位设置一致。

原理与设计说明

LED 流水灯的核心是分频计数器。50 MHz 时钟周期 20 ns,要得到 1 Hz 周期(1 秒),需要计数 50,000,000 个时钟周期。计数器宽度为 26 位(2^26 = 67,108,864,足够覆盖)。流水效果通过循环左移实现,每次溢出时 led 值左移一位,最低位补最高位。

UART 通信的关键是波特率生成和位采样。发送模块使用一个计数器在 50 MHz 时钟下产生 115200 Hz 的使能信号(每 434 个时钟周期产生一个脉冲)。数据以 10 位帧格式发送:1 位起始位(低电平)、8 位数据位(LSB 先发)、1 位停止位(高电平)。接收模块同样使用计数器,但为了正确采样,通常需要在数据位中间采样。本设计简化了采样逻辑,直接在每个波特率周期末尾采样,对于 115200 波特率在 50 MHz 时钟下仍能可靠工作(采样窗口误差 < 1%)。

回环逻辑在顶层模块中实现:当 uart_rx 检测到完整一帧数据时,rx_done 脉冲触发,将 rx_data 赋值给 tx_data,并启动 tx_start。发送模块在 tx_busy 为低时接收新数据,完成后自动置位 tx_busy。这种握手方式避免了数据冲突。

Trade-off 分析:本设计采用计数器分频而非 PLL,优点是简单、可移植,缺点是占用更多逻辑资源。对于 50 MHz 时钟和 115200 波特率,计数器宽度为 9 位(2^9=512),资源开销很小。如果使用 PLL 生成精确时钟,可提高波特率精度(如 921600),但增加了时钟域约束复杂度。

验证与结果

验证项预期结果实际结果(示例)测量条件
LED 流水频率1 Hz1.002 Hz(误差 < 0.2%)示波器测量 led[0] 波形
UART 发送
标签:
本文原创,作者:二牛学FPGA,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
如需转载,请注明出处:https://z.shaonianxue.cn/43455.html
二牛学FPGA

二牛学FPGA

初级工程师
这家伙真懒,几个字都不愿写!
1.11K21.57W4.12W3.67W
分享:
成电国芯FPGA赛事课即将上线
2026年5月:Verilog基础语法速成,新手避坑指南
2026年5月:Verilog基础语法速成,新手避坑指南上一篇
2026年Q2 FPGA仿真工具对比:ModelSim vs VCS 选型与实施指南下一篇
2026年Q2 FPGA仿真工具对比:ModelSim vs VCS 选型与实施指南
相关文章
总数:1.17K
FPGA仿真效率提升指南:基于SystemVerilog的可重用测试平台设计与实现

FPGA仿真效率提升指南:基于SystemVerilog的可重用测试平台设计与实现

QuickStart:快速搭建可重用测试平台安装支持SystemVer…
技术分享
21天前
0
0
29
0
FPGA图像处理实战:基于Vivado HLS的实时边缘检测系统设计

FPGA图像处理实战:基于Vivado HLS的实时边缘检测系统设计

本指南旨在指导您完成一个基于VivadoHLS(高层次综合)的实时图像…
技术分享
27天前
0
0
52
0
FPGA软核处理器与商用嵌入式MPU选型指南:灵活性与性能的权衡实践

FPGA软核处理器与商用嵌入式MPU选型指南:灵活性与性能的权衡实践

在嵌入式系统架构设计中,选择基于FPGA的软核处理器(例如RISC-V)…
技术分享
25天前
0
0
40
0
评论表单游客 您好,欢迎参与讨论。
加载中…
评论列表
总数:0
FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
没有相关内容