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

2026年5月:FPGA基础模块设计顺序:计数器、状态机还是FIFO?

二牛学FPGA二牛学FPGA
技术分享
1小时前
0
0
5

Quick Start

  • 打开Vivado 2024.2(或更高版本),创建新工程,选择xc7a35ticsg324-1L(Artix-7)作为目标器件。
  • 新建一个Verilog源文件,命名为counter_fsm_fifo_top.v,作为顶层模块。
  • 编写一个8位同步计数器模块,支持使能(en)和同步复位(rst_n),输出计数结果cnt[7:0]
  • 编写一个三段式状态机模块,包含IDLE、RUN、DONE三个状态,控制计数器使能信号的启停。
  • 编写一个同步FIFO模块,深度16,数据位宽8,将状态机输出的使能信号作为写使能,计数器输出作为写数据。
  • 在顶层模块中例化上述三个模块,连接:状态机输出en→计数器en,计数器输出cnt→FIFO写数据wdata
  • 编写仿真testbench,驱动时钟100MHz、复位低有效,观察状态机跳转、计数器递增、FIFO写满后读出的波形。
  • 运行仿真,确认:复位后状态机进入IDLE,计数器清零,FIFO空标志有效;启动后状态机进入RUN,计数器从0计到255,FIFO写入16个数据后满;状态机进入DONE,停止写入。
  • 综合并实现,检查资源占用(LUT、FF、BRAM)和Fmax,确保无时序违例。
  • (可选)下载到FPGA开发板,通过LED或串口观察计数与FIFO状态。

前置条件与环境

项目推荐值说明替代方案
器件/板卡Xilinx Artix-7 xc7a35ticsg324-1L入门级FPGA,资源适中Intel Cyclone IV E / Lattice iCE40
EDA版本Vivado 2024.2支持SystemVerilog-2012,综合与仿真集成Vivado 2023.x / Quartus Prime 23.x
仿真器Vivado Simulator (Xsim)内置于Vivado,无需额外安装ModelSim / Questa / Verilator
时钟/复位100MHz单端时钟,低电平异步复位板上晶振或PLL生成50MHz / 差分时钟
接口依赖无外部接口(纯逻辑验证)仿真即可完成功能验证UART / SPI / LED输出
约束文件XDC文件,定义时钟周期10ns、输入输出延迟至少包含create_clock约束SDC(Quartus)

目标与验收标准

  • 功能点1:计数器独立工作——使能有效时每个时钟周期递增1,使能无效时保持,复位归零。
  • 功能点2:状态机控制流程——IDLE等待启动信号→RUN启动计数器→DONE(计数器达到255后自动进入)→等待复位后回到IDLE。
  • 功能点3:FIFO读写正确——写满16个数据后满标志置位,读使能有效时依次读出,空标志正确。
  • 性能指标——综合后Fmax不低于200MHz(示例值,以实际工程为准),逻辑资源占用不超过器件总量的5%。
  • 验收方式——仿真波形显示完整流程,无X态或毛刺;综合报告无时序违例;资源报告显示LUT≤200、FF≤150、BRAM≤1。

实施步骤

阶段一:工程结构与计数器模块

  • 创建工程目录结构:src/放RTL,sim/放testbench,constr/放XDC。
  • 计数器模块代码(8位同步使能、同步复位):
module counter_8bit (
    input  wire       clk,
    input  wire       rst_n,
    input  wire       en,
    output reg  [7:0] cnt
);
    always @(posedge clk) begin
        if (!rst_n)
            cnt <= 8'd0;
        else if (en)
            cnt <= cnt + 1'b1;
        else
            cnt <= cnt;
    end
endmodule

逐行说明

  • 第1行:模块声明,定义端口方向与位宽。clk和rst_n为全局信号,en为输入使能,cnt为8位寄存器输出。
  • 第7行:always块对时钟上升沿敏感,实现同步逻辑。
  • 第8-9行:同步复位——当rst_n为低时,cnt清零。注意:同步复位综合为LUT+FF结构,比异步复位更安全,避免复位释放时的亚稳态。
  • 第10-11行:使能有效时递增,否则保持。保持写法(cnt <= cnt)综合时会生成反馈MUX,不会产生额外寄存器。
  • 第12行:else分支覆盖所有情况,避免锁存器推断。
  • 常见坑与排查
  • 若仿真中cnt不递增,检查en信号是否在仿真中正确驱动(常见错误:en保持为0或X)。
  • 若综合后cnt被优化掉,检查是否未在顶层例化或输出悬空。

阶段二:三段式状态机模块

  • 状态机采用三段式写法(状态寄存器、次态逻辑、输出逻辑),避免组合反馈与毛刺。
  • 代码:
module fsm_ctrl (
    input  wire       clk,
    input  wire       rst_n,
    input  wire       start,
    input  wire       cnt_done,   // 来自计数器,cnt == 8'd255
    output reg        en_cnt      // 使能计数器
);
    localparam IDLE = 2'b00,
               RUN  = 2'b01,
               DONE = 2'b10;
    reg [1:0] state, next_state;

    // 第一段:状态寄存器
    always @(posedge clk) begin
        if (!rst_n)
            state &lt;= IDLE;
        else
            state &lt;= next_state;
    end

    // 第二段:次态逻辑(组合)
    always @(*) begin
        case (state)
            IDLE: next_state = start ? RUN : IDLE;
            RUN:  next_state = cnt_done ? DONE : RUN;
            DONE: next_state = IDLE;  // 等待外部复位或自动回IDLE
            default: next_state = IDLE;
        endcase
    end

    // 第三段:输出逻辑(组合)
    always @(*) begin
        case (state)
            IDLE: en_cnt = 1'b0;
            RUN:  en_cnt = 1'b1;
            DONE: en_cnt = 1'b0;
            default: en_cnt = 1'b0;
        endcase
    end
endmodule

逐行说明

  • 第1-7行:模块声明,start为启动信号,cnt_done来自计数器(当cnt==255时置1),en_cnt输出到计数器使能。
  • 第9-10行:本地参数定义三个状态,使用2位编码。
  • 第12行:状态寄存器为reg类型,综合为FF。
  • 第15-19行:第一段时序逻辑,复位后进入IDLE。
  • 第22-28行:第二段组合逻辑,根据当前状态和输入决定下一状态。注意:组合逻辑中使用阻塞赋值(=),避免仿真竞争。
  • 第31-37行:第三段组合逻辑,输出en_cnt。RUN状态时输出1,其他状态输出0。
  • 三段式优点:输出与状态寄存器分离,避免组合输出毛刺;综合后输出路径短,Fmax高。
  • 常见坑与排查
  • 若状态机卡在某个状态不跳转,检查cnt_done信号是否在仿真中正确产生(计数器达到255时需拉高一个周期)。
  • 若en_cnt输出有毛刺,检查输出逻辑是否为纯组合且无反馈——三段式设计已规避此问题。

阶段三:同步FIFO模块

  • 同步FIFO深度16,使用BRAM实现存储体,地址指针用二进制计数器。
  • 代码(核心部分,省略空/满标志生成):
module sync_fifo #(
    parameter DEPTH = 16,
    parameter WIDTH = 8
)(
    input  wire            clk,
    input  wire            rst_n,
    input  wire            wr_en,
    input  wire [WIDTH-1:0] wdata,
    input  wire            rd_en,
    output reg  [WIDTH-1:0] rdata,
    output wire            full,
    output wire            empty
);
    localparam PTR_W = $clog2(DEPTH);  // 地址位宽 = 4
    reg [PTR_W-1:0] wr_ptr, rd_ptr;
    reg [WIDTH-1:0] mem [0:DEPTH-1];

    // 写指针
    always @(posedge clk) begin
        if (!rst_n)
            wr_ptr &lt;= 0;
        else if (wr_en &amp;&amp; !full)
            wr_ptr &lt;= wr_ptr + 1'b1;
    end

    // 读指针
    always @(posedge clk) begin
        if (!rst_n)
            rd_ptr &lt;= 0;
        else if (rd_en &amp;&amp; !empty)
            rd_ptr &lt;= rd_ptr + 1'b1;
    end

    // 写数据到mem
    always @(posedge clk) begin
        if (wr_en &amp;&amp; !full)
            mem[wr_ptr] &lt;= wdata;
    end

    // 读数据(组合输出)
    always @(*) begin
        rdata = mem[rd_ptr];
    end

    // 空/满标志(组合逻辑)
    assign full  = (wr_ptr == rd_ptr - 1'b1);  // 简化版,实际需考虑深度
    assign empty = (wr_ptr == rd_ptr);
endmodule

逐行说明

  • 第1-3行:参数化模块,DEPTH=16,WIDTH=8,方便复用。
  • 第4-12行:端口声明,包含写/读使能、数据、空满标志。
  • 第14行:$clog2函数计算地址位宽,16深度需要4位地址。
  • 第15行:声明写指针和读指针寄存器。
  • 第16行:声明存储器mem,综合为BRAM(深度16时也可用分布式RAM)。
  • 第19-23行:写指针递增逻辑,仅在wr_en有效且非满时递增。
  • 第26-30行:读指针递增逻辑,类似。
  • 第33-36行:写数据到mem,使用写指针作为地址。
  • 第39-41行:读数据组合输出,直接索引mem。注意:组合读会占用LUT资源,若需BRAM同步读,应改为时序读(always @(posedge clk))。
  • 第44-45行:空满标志简化版。full条件为wr_ptr == rd_ptr - 1(即写指针追上读指针的前一个位置),empty为wr_ptr == rd_ptr。实际工程中需处理指针回绕(例如使用格雷码或扩展一位)。
  • 常见坑与排查
  • 若full标志在FIFO未满时误置位,检查wr_ptr与rd_ptr的比较逻辑是否考虑了回绕(本简化版在深度为2的幂时可用,但边界条件需验证)。
  • 若rdata出现X态,检查读地址rd_ptr是否在复位后未初始化(本代码已复位归零)。

阶段四:顶层连接与仿真验证

  • 在顶层模块中例化三个模块:
module top (
    input  wire       clk,
    input  wire       rst_n,
    input  wire       start,
    output wire [7:0] cnt_out,
    output wire [7:0] fifo_data,
    output wire       full,
    output wire       empty
);
    wire en_cnt;
    wire cnt_done;
    wire [7:0] cnt;

    // 计数器达到255时产生done信号
    assign cnt_done = (cnt == 8'd255);

    counter_8bit u_cnt (
        .clk   (clk),
        .rst_n (rst_n),
        .en    (en_cnt),
        .cnt   (cnt)
    );

    fsm_ctrl u_fsm (
        .clk      (clk),
        .rst_n    (rst_n),
        .start    (start),
        .cnt_done (cnt_done),
        .en_cnt   (en_cnt)
    );

    sync_fifo #(.DEPTH(16), .WIDTH(8)) u_fifo (
        .clk   (clk),
        .rst_n (rst_n),
        .wr_en (en_cnt),
        .wdata (cnt),
        .rd_en (1'b0),   // 本示例不读,仅验证写满
        .rdata (),
        .full  (full),
        .empty (empty)
    );

    assign cnt_out  = cnt;
    assign fifo_data = cnt;  // 仅用于观察
endmodule

逐行说明

  • 第1-8行:顶层模块端口,start为外部启动信号,cnt_out和fifo_data用于观察。
  • 第10-13行:内部连线声明,en_cnt连接状态机输出和计数器使能,cnt_done由组合逻辑产生。
  • 第15行:组合比较器,当cnt==255时拉高cnt_done。注意:此信号会持续一个时钟周期(因为cnt在255时保持,直到en_cnt无效)。
  • 第17-22行:例化计数器。
  • 第24-31行:例化状态机。
  • 第33-42行:例化FIFO,wr_en连接en_cnt(即RUN状态下每个时钟写入一次),rd_en固定为0(不读),full和empty输出到顶层。
  • 第44-45行:输出赋值,便于仿真观察。
  • 常见坑与排查
  • 若仿真中full信号从未拉高,检查wr_en是否在每个时钟周期都有效(状态机在RUN状态应持续输出en_cnt=1)。
  • 若cnt_done信号出现毛刺,检查cnt==255的比较是否为组合逻辑——本设计无问题,但若cnt由多个源驱动则可能产生毛刺。

阶段五:综合与实现

  • 在Vivado中运行综合(Synthesis),查看综合报告中的资源占用和时序预估。
  • 运行实现(Implementation),查看布局布线后的时序报告,确保建立时间满足(WNS≥0)。
  • 若出现时序违例,检查时钟约束是否正确(create_clock -period 10.000 [get_ports clk])。
  • 若资源占用过高,检查是否误用了大量LUT(例如FIFO读端口使用了组合输出,改为时序输出可降低LUT用量)。

原理与设计说明

为什么学习顺序是计数器→状态机→FIFO?这三个模块分别对应FPGA设计的三个核心抽象层次:

  • 计数器:最基础的时序逻辑,理解同步复位、使能、寄存器行为。它是所有复杂时序的起点(分频、定时、地址生成)。
  • 状态机:控制逻辑的核心,将组合逻辑与时序逻辑分离(三段式),理解状态编码、次态生成、输出逻辑。它解决“什么时候做什么”的问题。
  • FIFO:数据流管理的典型,涉及存储体(BRAM/分布式RAM)、指针管理、空满标志生成。它解决“数据如何暂存与传输”的问题,是跨时钟域(异步FIFO)的基础。

关键trade-off解析

  • 资源 vs Fmax:计数器使用LUT+FF实现,Fmax高;FIFO使用BRAM可节省LUT但增加延迟(BRAM读延迟1-2周期)。本示例中FIFO深度16,使用分布式RAM(LUT)也可,但深度超过64时BRAM更优。
  • 吞吐 vs 延迟:状态机控制计数器连续写入FIFO,吞吐为1数据/周期;若需暂停(如FIFO满),状态机需插入等待状态,降低吞吐但防止数据溢出。
  • 易用性 vs 可移植性:三段式状态机比一段式(所有逻辑写在一个always块)更易读、易调试,但代码量稍大。同步FIFO的简化版空满标志(使用二进制指针比较)在深度为2的幂时有效,但移植到异步FIFO时需改用格雷码。

为什么先学计数器再学状态机?因为状态机的输出(如en_cnt)驱动计数器,而计数器的输出(cnt_done)又反馈给状态机——形成闭环控制。理解这个反馈路径是理解数字系统控制流的关键。

验证与结果

指标测量值(示例)测量条件
Fmax(计数器+状态机)350 MHzVivado 2024.2, Artix-7 -1L, 无FIFO
Fmax(含FIFO)280 MHzF
标签:
本文原创,作者:二牛学FPGA,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
如需转载,请注明出处:https://z.shaonianxue.cn/43466.html
二牛学FPGA

二牛学FPGA

初级工程师
这家伙真懒,几个字都不愿写!
1.11K21.56W4.12W3.67W
分享:
成电国芯FPGA赛事课即将上线
FPGA入门分阶段训练法:从组合逻辑到时序电路的设计与验证指南
FPGA入门分阶段训练法:从组合逻辑到时序电路的设计与验证指南上一篇
2026年Q2:先学仿真还是先学约束?FPGA新手的时间分配建议下一篇
2026年Q2:先学仿真还是先学约束?FPGA新手的时间分配建议
相关文章
总数:1.17K
Verilog中阻塞与非阻塞赋值综合结果深度对比

Verilog中阻塞与非阻塞赋值综合结果深度对比

QuickStart准备Vivado2023.2或更高版本,新建…
技术分享
3天前
0
0
13
0
Verilog FSM 设计实战指南:三段式与单段式对比与实现

Verilog FSM 设计实战指南:三段式与单段式对比与实现

QuickStart安装Vivado或QuartusPrime…
技术分享
18天前
0
0
35
0
FPGA工程师面向2026年新兴领域的技术准备与实践指南

FPGA工程师面向2026年新兴领域的技术准备与实践指南

随着异构计算架构的演进与边缘智能需求的爆发,FPGA凭借其独特的可重构性…
技术分享
24天前
0
0
66
0
评论表单游客 您好,欢迎参与讨论。
加载中…
评论列表
总数:0
FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
没有相关内容