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 <= IDLE;
else
state <= 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 <= 0;
else if (wr_en && !full)
wr_ptr <= wr_ptr + 1'b1;
end
// 读指针
always @(posedge clk) begin
if (!rst_n)
rd_ptr <= 0;
else if (rd_en && !empty)
rd_ptr <= rd_ptr + 1'b1;
end
// 写数据到mem
always @(posedge clk) begin
if (wr_en && !full)
mem[wr_ptr] <= 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 MHz | Vivado 2024.2, Artix-7 -1L, 无FIFO |
| Fmax(含FIFO) | 280 MHz | F标签:如需转载,请注明出处:https://z.shaonianxue.cn/43466.html ![]() ![]() ![]() ![]() Verilog中阻塞与非阻塞赋值综合结果深度对比![]() Verilog FSM 设计实战指南:三段式与单段式对比与实现![]() FPGA工程师面向2026年新兴领域的技术准备与实践指南加载中… |



