当前位置: 首页 > news >正文

第八章 FPGA 片内 FIFO 读写测试实验

第八章 FPGA 片内 FIFO 读写测试实验

实验原理

FIFO: First in, First out代表先进的数据先出 ,后进的数据后出。
只需通过 IP 核例化一个 FIFO ,根据 FIFO 的读写时序来写入和读取FIFO 中存储的数据 。
FIFO 的典型结构如下,主要分为读和写两部分,另外就是状态信号,空和满信号,同时还有数据的数量状态信号,与 RAM 最大的不同是 FIFO 没有地址线,不能进行随机地址读取数据。
在这里插入图片描述
在这里插入图片描述
标准FIFO读写时序,都是在时钟的上升沿进行操作。

写使能(wr_en)为高时写入FIFO数据,将满信号(almost_full)在只能写入一个数据时有效,写入后FIFO满信号(full)拉高,在full的情况下继续向FIFO写数据将会产生溢出信号(overflow)。
在这里插入图片描述

读使能(rd_en)为高时读取FIFO数据,数据在下一个周期有效,valid为数据有效信号。将空信号(almost_empty)表示只剩一个数据可读时有效,读取后空信号(empty)有效,继续读的话会产生下溢信号(underflow)。
在这里插入图片描述

FWFT模式(数据预取模式,提前取出一个数据)在rd_en有效时,数据不会延后一个周期。

实验步骤

  1. 创建工程
  2. 添加IP核,IP Catelog中搜索fifo,选择FIFO Generator
  3. 设置IP核,读写始终不一致选择“Independent Clocks Block RAM”,还有等等其他的设置,根据需要设置进行设置即可,可以参考文档
  4. 根据读写时序编写测试代码。

实验过程

测试代码

写端口非满一直写,读端口非空一直读。并且进行错误统计。

`timescale 1ns / 1ps module fifo_test 
( input       sys_clk_p,            // 系统差分时钟正端,200MHzinput       sys_clk_n,            // 系统差分时钟负端,200MHz  input       rst_n                 // 全局复位信号,低电平有效  
);  // FIFO控制信号reg  [15:0]     w_data;           // 写入FIFO的数据wire            wr_en;            // FIFO写使能信号wire            rd_en;            // FIFO读使能信号wire [15:0]     r_data;           // 从FIFO读出的数据wire            full;             // FIFO满标志信号wire            almost_full;      // FIFO将满标志信号wire            empty;            // FIFO空标志信号wire            almost_empty;     // FIFO将空标志信号wire [8:0]      rd_data_count;    // 可读数据个数计数器wire [8:0]      wr_data_count;    // 已写入数据个数计数器// 时钟和复位信号wire            clk_100M;         // PLL生成的100MHz时钟wire            clk_75M;          // PLL生成的75MHz时钟wire            locked;           // PLL锁定信号,锁定后为高电平wire            fifo_wr_rst_n;    // FIFO写时钟域复位信号,低电平有效wire            fifo_rd_rst_n;    // FIFO读时钟域复位信号,低电平有效wire            wr_clk;           // FIFO写时钟wire            rd_clk;           // FIFO读时钟reg [7:0]       wcnt;             // 写FIFO复位等待计数器reg [7:0]       rcnt;             // 读FIFO复位等待计数器// 错误监控和统计信号reg             overflow_error;   // 溢出错误标志:在FIFO满时仍然写入reg             underflow_error;  // 下溢错误标志:在FIFO空时仍然读取reg [31:0]      write_total;      // 总写入数据计数reg [31:0]      read_total;       // 总读取数据计数// =========================================================================// PLL时钟管理模块实例化// 功能:将输入的200MHz差分时钟转换为100MHz和75MHz单端时钟// =========================================================================clk_wiz_0 fifo_pll ( // 时钟输出端口.clk_out1(clk_100M),          // 输出100MHz时钟.clk_out2(clk_75M),           // 输出75MHz时钟// 状态和控制信号.reset(~rst_n),               // 复位信号,高电平有效(所以取反外部复位).locked(locked),              // PLL锁定输出,锁定后为高电平// 时钟输入端口(差分输入).clk_in1_p(sys_clk_p),        // 差分时钟正端输入.clk_in1_n(sys_clk_n)         // 差分时钟负端输入);         // 时钟分配assign wr_clk = clk_100M;         // 写时钟使用100MHzassign rd_clk = clk_75M;          // 读时钟使用75MHz// =========================================================================// 同步复位信号生成// 重要:为避免跨时钟域问题,需要为每个时钟域生成独立的同步复位信号// =========================================================================reg [2:0] wr_reset_sync = 3'b0;   // 写时钟域复位同步寄存器reg [2:0] rd_reset_sync = 3'b0;   // 读时钟域复位同步寄存器// 写时钟域复位同步逻辑always @(posedge wr_clk or negedge locked) beginif (!locked) begin// PLL未锁定时,保持复位状态wr_reset_sync <= 3'b0;end else begin// 使用移位寄存器实现同步,确保复位信号稳定wr_reset_sync <= {wr_reset_sync[1:0], 1'b1};endend// 读时钟域复位同步逻辑always @(posedge rd_clk or negedge locked) beginif (!locked) begin// PLL未锁定时,保持复位状态rd_reset_sync <= 3'b0;end else begin// 使用移位寄存器实现同步,确保复位信号稳定rd_reset_sync <= {rd_reset_sync[1:0], 1'b1};endend// 分配同步后的复位信号assign fifo_wr_rst_n = wr_reset_sync[2];  // 取同步链的最后一级作为稳定复位assign fifo_rd_rst_n = rd_reset_sync[2];// =========================================================================// 写FIFO状态机// 功能:控制FIFO的写入时序,确保复位后稳定工作// =========================================================================// 状态定义localparam [1:0] W_IDLE = 2'b01;  // 空闲状态:等待复位稳定localparam [1:0] W_FIFO = 2'b10;  // 写入状态:持续向FIFO写入数据reg [1:0] write_state;            // 当前写状态reg [1:0] next_write_state;       // 下一写状态// 写状态机时序逻辑always @(posedge wr_clk or negedge fifo_wr_rst_n) begin  if (!fifo_wr_rst_n) // 复位时进入空闲状态write_state <= W_IDLE; else // 正常工作时状态转移write_state <= next_write_state; end // 写状态机组合逻辑always @(*) begin case (write_state) W_IDLE: begin // 等待80个时钟周期确保系统稳定,然后进入写入状态if (wcnt == 8'd79)               next_write_state <= W_FIFO; else next_write_state <= W_IDLE; end W_FIFO: // 进入写入状态后保持,持续写入数据next_write_state <= W_FIFO;      default: // 默认返回空闲状态next_write_state <= W_IDLE; endcase end // 写状态机等待计数器always @(posedge wr_clk or negedge fifo_wr_rst_n) begin  if (!fifo_wr_rst_n) // 复位时清零计数器wcnt <= 8'd0; else if (write_state == W_IDLE) // 在空闲状态时递增计数器wcnt <= wcnt + 1'b1; else // 进入写入状态后清零计数器wcnt <= 8'd0; end // =========================================================================// 写使能和数据生成逻辑// =========================================================================// 写使能信号生成(寄存器输出避免毛刺)reg wr_en_reg;always @(posedge wr_clk or negedge fifo_wr_rst_n) begin if (!fifo_wr_rst_n) wr_en_reg <= 1'b0; else // 在写入状态且FIFO不满时产生写使能wr_en_reg <= (write_state == W_FIFO) && !almost_full; end assign wr_en = wr_en_reg;// 写入数据生成:简单的递增计数器always @(posedge wr_clk or negedge fifo_wr_rst_n) begin if (!fifo_wr_rst_n) // 复位时从1开始w_data <= 16'd1; else if (wr_en) // 每次写使能有效时数据加1w_data <= w_data + 1'b1; end // =========================================================================// 读FIFO状态机// 功能:控制FIFO的读取时序,确保复位后稳定工作// =========================================================================// 状态定义localparam [1:0] R_IDLE = 2'b01;  // 空闲状态:等待复位稳定localparam [1:0] R_FIFO = 2'b10;  // 读取状态:持续从FIFO读取数据reg [1:0] read_state;             // 当前读状态reg [1:0] next_read_state;        // 下一读状态// 读状态机时序逻辑always @(posedge rd_clk or negedge fifo_rd_rst_n) begin if (!fifo_rd_rst_n) // 复位时进入空闲状态read_state <= R_IDLE; else // 正常工作时状态转移read_state <= next_read_state; end // 读状态机组合逻辑always @(*) begin case (read_state) R_IDLE: begin // 等待60个时钟周期确保系统稳定,然后进入读取状态if (rcnt == 8'd59)              next_read_state <= R_FIFO; else next_read_state <= R_IDLE; end R_FIFO:  // 进入读取状态后保持,持续读取数据next_read_state <= R_FIFO;      default: // 默认返回空闲状态next_read_state <= R_IDLE; endcase end // 读状态机等待计数器// 重要修改:修复了原代码中的跨时钟域问题,现在使用正确的read_statealways @(posedge rd_clk or negedge fifo_rd_rst_n) begin  if (!fifo_rd_rst_n) // 复位时清零计数器rcnt <= 8'd0; else if (read_state == R_IDLE) // 在空闲状态时递增计数器rcnt <= rcnt + 1'b1; else // 进入读取状态后清零计数器rcnt <= 8'd0; end// =========================================================================// 读使能信号生成// =========================================================================// 读使能信号生成(寄存器输出避免毛刺)reg rd_en_reg;always @(posedge rd_clk or negedge fifo_rd_rst_n) begin if (!fifo_rd_rst_n) rd_en_reg <= 1'b0; else // 在读取状态且FIFO不空时产生读使能rd_en_reg <= (read_state == R_FIFO) && !almost_empty; end assign rd_en = rd_en_reg;// =========================================================================// 错误监控和统计逻辑// 功能:检测FIFO操作中的错误并统计性能数据// =========================================================================// 写时钟域错误监控always @(posedge wr_clk or negedge fifo_wr_rst_n) beginif (!fifo_wr_rst_n) begin// 复位时清零错误标志和计数器overflow_error <= 1'b0;write_total <= 32'b0;end else begin// 检测溢出错误:在FIFO满时仍然尝试写入overflow_error <= wr_en && full;// 统计成功写入的数据总数if (wr_en && !full) beginwrite_total <= write_total + 1'b1;endendend// 读时钟域错误监控always @(posedge rd_clk or negedge fifo_rd_rst_n) beginif (!fifo_rd_rst_n) begin// 复位时清零错误标志和计数器underflow_error <= 1'b0;read_total <= 32'b0;end else begin// 检测下溢错误:在FIFO空时仍然尝试读取underflow_error <= rd_en && empty;// 统计成功读取的数据总数if (rd_en && !empty) beginread_total <= read_total + 1'b1;endendend// =========================================================================// FIFO IP核实例化// 重要:FIFO IP核的复位通常是高电平有效,所以需要将我们的低有效复位取反// =========================================================================fifo_ip fifo_ip_inst  ( .rst            (~fifo_wr_rst_n),       // 复位信号,高电平有效(对FIFO IP取反).wr_clk         (wr_clk),               // 写时钟输入.rd_clk         (rd_clk),               // 读时钟输入.din            (w_data),               // 写入数据输入 [15:0].wr_en          (wr_en),                // 写使能输入.rd_en          (rd_en),                // 读使能输入.dout           (r_data),               // 读取数据输出 [15:0].full           (full),                 // 满标志输出.almost_full    (almost_full),          // 将满标志输出.empty          (empty),                // 空标志输出.almost_empty   (almost_empty),         // 将空标志输出.rd_data_count  (rd_data_count),        // 可读数据计数输出 [8:0].wr_data_count  (wr_data_count)         // 已写数据计数输出 [8:0]); // =========================================================================// 逻辑分析仪(ILA)调试接口// 功能:用于在线调试和信号观测// =========================================================================// 写通道逻辑分析仪ila_fifo ila_wfifo ( .clk(wr_clk),                  // 采样时钟:写时钟.probe0(w_data),               // 探针0:写入数据.probe1(wr_en),                // 探针1:写使能信号.probe2(full),                 // 探针2:满标志.probe3(wr_data_count),        // 探针3:写数据计数.probe4(overflow_error),       // 探针4:溢出错误标志.probe5(write_total[15:0])     // 探针5:写入总数(低16位)); // 读通道逻辑分析仪ila_fifo ila_rfifo ( .clk(rd_clk),                  // 采样时钟:读时钟.probe0(r_data),               // 探针0:读取数据.probe1(rd_en),                // 探针1:读使能信号.probe2(empty),                // 探针2:空标志.probe3(rd_data_count),        // 探针3:读数据计数.probe4(underflow_error),      // 探针4:下溢错误标志.probe5(read_total[15:0])      // 探针5:读取总数(低16位)); endmodule

ILA IP核配置

在这里插入图片描述

在线调试

读的数据都是连续的,这里没有空的情况,因为写的速率较快。
在这里插入图片描述

使能在将满时拉低,停止数据写入。
在这里插入图片描述

问题记录

1. 写数据时产生overflow_error错误。

现象:
在这里插入图片描述
写数据的速率要大于读数据的速率,会出现fifo为满的情况,但是不应该出现溢出的错误。
原因

wr_en_reg <= (write_state == W_FIFO) && !full;
时钟周期n:   full = 0, wr_en = 1  → 开始写入
时钟周期n+1: full = 1 (FIFO变满),但wr_en仍然有效→ 在full=1时仍然写入 → overflow_error = 1

写使能信号会晚一个时钟周期拉低,满信号拉高一个周期后写使能才会拉低,会出现一次溢出错误。

解决方案

可以改为组合逻辑,但是有一些风险。

使用FIFO的将满信号来控制读取,可以将写使能提前一个信号拉低,正好解决问题。

同理,读使能也有类似的问题,出现下溢,改用将空信号控制。

常见问题和误区(来自DeepSeek)

1. 深度计算误区

误区:简单估算深度

// 错误:深度 = 写入速率 - 读取速率
localparam FIFO_DEPTH = 100; // 随意选择// 正确:考虑最坏情况的突发
// 计算公式:深度 ≥ (写入速率 × 突发长度) / 读取速率
localparam BURST_LENGTH = 128;
localparam WR_RATE = 100; // MHz
localparam RD_RATE = 50;  // MHz
localparam FIFO_DEPTH = (WR_RATE * BURST_LENGTH) / RD_RATE + 1; // 257

正确的深度计算考虑因素:

  • 最大突发数据量
  • 读写时钟频率比
  • 背压机制延迟
  • 安全余量(通常+20%)

2. 空满标志理解错误

误区:立即响应空满标志

// 错误理解:empty变低后立即可以读取
always @(posedge clk) beginif (!empty) begindata_out <= fifo_dout;  // 可能读到无效数据!rd_en <= 1'b1;end
end// 正确:空标志变低后的下一个周期才能读取
always @(posedge clk) beginif (!empty) beginrd_en <= 1'b1;          // 当前周期使能读取end else beginrd_en <= 1'b0;end
end// fifo_dout在rd_en有效后的下一个周期才有效

3. 复位处理不当

误区:复位期间继续操作

// 危险:复位期间没有停止读写
always @(posedge clk) beginwr_en <= data_valid;  // 复位期间可能继续写入rd_en <= need_data;   // 复位期间可能继续读取
end// 正确:复位时禁用所有操作
always @(posedge clk or posedge rst) beginif (rst) beginwr_en <= 1'b0;rd_en <= 1'b0;end else beginwr_en <= data_valid && !full;rd_en <= need_data && !empty;end
end

4. 跨时钟域FIFO的时钟关系

误区:任意时钟频率比

// 错误:认为任何时钟比都能工作
fifo_async your_instance (.rst(rst),        // 异步复位,危险!.wr_clk(clk_200M),.rd_clk(clk_1M),  // 200:1 的时钟比可能有问题.din(din),.wr_en(wr_en),.rd_en(rd_en),.dout(dout),.full(full),.empty(empty)
);

异步FIFO的限制:

  • 通常建议读写时钟比 < 10:1
  • 极端时钟比需要特殊设计
  • 复位必须是写时钟域的同步复位

5. 读写使能控制错误

误区:持续使能读写

// 错误:不考虑空满状态
assign wr_en = data_valid;  // 可能在全状态写入,数据丢失!
assign rd_en = data_needed; // 可能在空状态读取,读到垃圾数据!// 正确:受空满标志约束
assign wr_en = data_valid && !full;
assign rd_en = data_needed && !empty;

6. 几乎空/几乎满标志误用

误区:当作精确空满使用

// 错误理解
if (almost_full) stop_writing_entirely;  // 过早停止
if (almost_empty) start_reading_franticly; // 过度反应// 正确使用
if (almost_full) reduce_writing_gradually;  // 渐进调整
if (almost_empty) request_more_data_soon;   // 提前预警

7. 数据宽度转换问题

误区:随意改变数据位宽

// 32位到8位FIFO,但写入模式错误
always @(posedge wr_clk) beginif (data_valid) begindin <= big_data;     // 一次写入32位wr_en <= 1'b1;end
end// 读取端期望4个8位数据
always @(posedge rd_clk) beginif (!empty) beginsmall_data <= dout;  // 每次读取8位rd_en <= 1'b1;end
end
// 问题:位宽不匹配导致数据错位!

8. 复位释放时机错误

误区:复位释放后立即操作

// 错误:复位释放后第一个周期就写入
always @(posedge clk) beginif (rst) begin// 复位中end else beginwr_en <= 1'b1;  // 复位刚结束就写入,可能不稳定end
end// 正确:等待FIFO初始化完成
reg [2:0] reset_counter;
always @(posedge clk or posedge rst) beginif (rst) beginreset_counter <= 3'b0;wr_en <= 1'b0;end else beginif (reset_counter != 3'b111) beginreset_counter <= reset_counter + 1;wr_en <= 1'b0;end else beginwr_en <= data_valid && !full;endend
end

9. 背压机制设计错误

误区:简单的停止信号

// 简单但不高效的背压
assign stall_previous_stage = full;  // 只在全满时停止// 更好的背压策略
assign flow_control = (fifo_usage > 80_PERCENT) ? SLOW_DOWN : (fifo_usage < 20_PERCENT) ? SPEED_UP : NORMAL;

10. 仿真 vs 实际行为差异

误区:仿真通过就认为正确

// 仿真可能工作的危险代码
initial begin#100; // 固定延迟start_test;
end// 实际硬件中的问题:
// - 空满标志的建立保持时间
// - 跨时钟域亚稳态
// - 复位毛刺

11. 状态监控缺失

误区:不监控FIFO健康状态

// 缺乏监控
fifo your_fifo (.wr_en(wr_en),.rd_en(rd_en)// 没有错误检测!
);// 应该添加:
reg overflow_error;
reg underflow_error;
reg almost_overflow;always @(posedge clk) beginoverflow_error <= wr_en && full;underflow_error <= rd_en && empty;almost_overflow <= (fifo_count > ALMOST_FULL_THRESHOLD);
end

12. 标准FIFO IP核配置误区

常见配置错误:

  • 复位类型:选择异步复位但实际需要同步
  • 使能信号:误配置为高有效/低有效
  • 输出寄存器:忘记使能导致时序违规
  • 握手信号:误用valid/ready协议

13. 性能优化误区

误区:过度优化

// 过早优化:复杂的流水线
always @(posedge clk) beginstage1 <= ...;stage2 <= stage1;stage3 <= stage2;  // 增加了不必要的延迟
end// 应该先确保功能正确,再考虑优化

14. 测试覆盖不足

容易遗漏的测试场景:

  • 边界情况:在深度-1和深度处操作
  • 同时读写:空满边界的同时操作
  • 时钟域交叉:极端频率比测试
  • 复位恢复:运行中复位和恢复
  • 背压测试:长时间背压下的行为

15. 调试技巧和最佳实践

添加调试监控:

// FIFO使用率监控
reg [31:0] write_count, read_count;
always @(posedge wr_clk) beginif (wr_en && !full) write_count <= write_count + 1;
end
always @(posedge rd_clk) beginif (rd_en && !empty) read_count <= read_count + 1;
end// 性能计数器
reg [31:0] full_count, empty_count;
always @(posedge wr_clk) beginif (full) full_count <= full_count + 1;
end
always @(posedge rd_clk) beginif (empty) empty_count <= empty_count + 1;
end

总结:FIFO设计检查清单

  • 深度计算 ✓ 考虑最坏情况突发
  • 时钟关系 ✓ 检查时钟频率比
  • 复位策略 ✓ 同步复位,适当延迟
  • 空满控制 ✓ 正确理解标志时序
  • 错误处理 ✓ 溢出、下溢检测
  • 性能监控 ✓ 使用率统计
  • 充分测试 ✓ 覆盖边界情况
  • 文档记录 ✓ 记录设计假设和限制

参考资料

  1. FIFO Generator v13.2(官方文档)
  2. Xilinx IP解析之FIFO Generator v13.2
http://www.dtcms.com/a/453471.html

相关文章:

  • 2025年--Lc170--H289. 生命游戏(矩阵)--Java版
  • FC和SFC的原版说明书(扫描的PDF)
  • 网站建设方案文库我们是设计师 网站建设专家
  • 做网站自己买服务器好还是用别人的柳州企业网站建设
  • 震荡市中MACD-KDJ组合的动态参数优化思路
  • 一文了解解耦 Prefilling 与 Decoding(PD 分离)以及典型的 PD 分离方案
  • petri网自学(四)
  • 海思Hi3516CV610/Hi3516CV608开发笔记之环境搭建和固件编译
  • 学生化残差(Studentized Residual):概念、计算与应用
  • 网站建设杭州做rap的网站
  • 华为交换机实战配置案例:从基础接入到核心网络
  • OpenCV(四):视频采集与保存
  • 证券业智能化投研与分布式交易系统架构:全球发展现状、技术创新与未来趋势研究
  • AI Agent竞争进入下半场:模型只是入场券,系统架构决定胜负
  • 图书商城网站开发的目的网页设计实训报告总结1500字
  • 做俄语网站做网站傻瓜软件
  • 兼具本地式与分布式优势、针对大类通用型Web漏洞、插件外部动态化导入的轻量级主被动扫描器
  • 第4章 文件管理
  • JavaScript初识及基本语法讲解
  • RabbitMQ中Consumer的可靠性
  • 自学网站建设作业抖音代运营公司收费
  • drupal做虚拟发货网站做网站如何将一张图片直接变体
  • 监控系统1 - 项目框架 | 线程邮箱
  • CTFHub SQL注入通关笔记3:报错注入(手注法+脚本法)
  • 开源UML工具完全指南:从图形化建模到文本驱动绘图
  • 优秀网站设计欣赏北京公司网站建设公司
  • 基于 Python 构建的安全 gRPC 服务——TLS、mTLS 与 Casbin 授权实战
  • 【Java核心技术/IO】35道Java IO面试题与答案
  • ICT 数字测试原理 10 - -VCL 向量如何执行之数字单元
  • 网站目录爬行wordpress怎么做信息分类