赞
踩
目录
说明:
本人使用的是野火家Xilinx Spartan6系列开发板及配套教程,写博客记录自己的学习。
开发软件:ise14.7 仿真:modelsim 10.5
FIFO(First In First Out,即先入先出),是一种数据缓冲器,用来实现数据先入先出的读写。与 ROM 或 RAM 的按地址读写方式不同, FIFO 的读写遵循“先进先出”的原则,即数据按顺序写入 FIFO,先被写入的数据同样在读取的时候先被读出,所以 FIFO存储器没有地址线。 FIFO 有一个写端口和一个读端口外部无需使用者控制地址,使用方便。
FIFO 存储器主要是作用为缓存,应用在同步时钟系统和异步时钟系统中,在很多的设计中都会使用如:多比特数据做跨时钟域的转换、前后带宽不同步等都用到了异步FIFO,示意图如下。 FIFO 根据读写时钟是否相同,分为 SCFIFO(同步 FIFO)和 DCFIFO(异步FIFO),SCFIFO 的读写为同一时钟,应用在同步时钟系统中; DCFIFO 的读写时钟不同,应用在异步时钟系统中。
配置过程比较简单,故不展示这里注意选择时钟统一。
实验目标:实现数据0-255的数据缓冲。
- `timescale 1ns/1ns
-
- module fifo
- (
- input wire sys_clk ,
- input wire [7:0] pi_data , //输入数据
- input wire pi_flag , //输入数据有效标志信号
- input wire rd_en , //FIFO读数据有效信号
-
- output wire empty , //FIFO空标志信号,高有效
- output wire full , //FIFO满标志信号,高有效
- output wire [7:0] po_data , //输出数据
- output wire [7:0] data_count //FIFO中存在的数据个数
- );
- //调用FIFOip核
- scfifo_256X8 scfifo_256X8_inst
- (
- .clk (sys_clk ),
- .din (pi_data ),
- .wr_en (pi_flag ),
- .rd_en (rd_en ),
-
- .dout (po_data ),
- .full (full ),
- .empty (empty ),
- .data_count(data_count)
- );
- endmodule
仿真代码:
- `timescale 1ns/1ns
-
- module tb_fifo();
-
- reg sys_clk ;
- reg [7:0] pi_data ;
- reg pi_flag ;
- reg rd_en ;
- reg sys_rst_n ;
- reg [1:0] cnt_baud ;
-
- wire [7:0] po_data ;
- wire empty ;
- wire full ;
- wire [7:0] data_count ;
-
- initial begin
- sys_clk = 1'b1;
- sys_rst_n <= 1'b0;
- #100;
- sys_rst_n <= 1'b1;
- end
- always #10 sys_clk = ~sys_clk;
- //cnt_baud:计数从0到3的计数器,用于产生输入数据间的间隔
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- cnt_baud <= 2'b0;
- else if(&cnt_baud == 1'b1)
- cnt_baud <= 2'b0;
- else
- cnt_baud <= cnt_baud + 1'b1;
-
- //pi_flag:输入数据写请求信号
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- pi_flag <= 1'b0;
- //每4个时钟周期且没有读请求时产生一个数据有效标志信号
- else if((cnt_baud == 2'd0) && (rd_en == 1'b0))
- pi_flag <= 1'b1;
- else
- pi_flag <= 1'b0;
-
- //pi_data:输入数据
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- pi_data <= 8'b0;
- else if((pi_data == 8'd225) && (pi_flag == 1'b1))
- pi_data <= 8'b0;
- else if(pi_flag == 1'b1) //每当pi_flag有效时产生一个数据
- pi_data <= pi_data + 1'b1;
- //rd_en:FIFO读请求信号
- always@(posedge sys_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- rd_en <= 1'b0;
- else if(full == 1'b1) //当FIFO中的数据存满时,开始读取FIFO中的数据
- rd_en <= 1'b1;
- else if(empty == 1'b1) //当FIFO中的数据被读空时停止读取FIFO中的数据
- rd_en <= 1'b0;
- fifo fifo_inst
- (
- .sys_clk (sys_clk ),
- .pi_data (pi_data ),
- .pi_flag (pi_flag ),
- .rd_en (rd_en ),
- .po_data (po_data ),
- .empty (empty ),
- .full (full ),
- .data_count (data_count )
- );
- endmodule
仿真结果:
从上图中可以看出full为高电平时,rd_en也在下一个节拍拉高,fifo开始读数据。
从上图中可以看出empty为高电平时,rd_en也在下一个节拍拉低并且pi_flag为高电平,fifo开始写数据。
配置过程比较简单,故不展示这里注意选择时钟统一。
实验目标:实现输入 256 个深度 8 位宽、 输出 128个深度 16 位宽的数据缓存器。
- `timescale 1ns/1ns
-
- module fifo
- (
- input wire [7:0] pi_data , //FIFO写入的数据,同步于wrclk时钟
- input wire wr_clk , //同步于FIFO 写数据 的时钟 50MHz
- input wire pi_flag , //输入数据有效标志信号,同步于wr_clk时钟
- output wire full , //满标志信号,高有效
- output wire [7:0] wr_data_count, //FIFO写端口中存在的数据个数,
- //同步于wrclk时钟
-
- output wire [15:0] po_data , //FIFO读出的数据,同步于rdclk时钟
- input wire rd_clk , //同步于FIFO 读数据 的时钟 25MHz
- input wire rd_en , //FIFO读请求信号,同步于rdclk时钟
- output wire empty , //空标志信号,高有效,
- output wire [6:0] rd_data_count //FIFO读端口中存在的数据个数,
- //同步于rdclk时钟
-
- );
- //调用FIFO ip核
- dcfifo_256x8to128x16 dcfifo_256x8to128x16_inst
- (
- .din (pi_data),
- .rd_clk (rd_clk ),
- .rd_en (rd_en ),
- .wr_clk (wr_clk ),
- .wr_en (pi_flag),
-
- .dout (po_data),
- .empty (empty ),
- .full (full ),
- .rd_data_count (rd_data_count),
- .wr_data_count (wr_data_count)
- );
- endmodule
仿真代码:
- `timescale 1ns/1ns
-
- module tb_fifo();
-
- reg wr_clk ;
- reg [7:0] pi_data ;
- reg pi_flag ;
- reg rd_clk ;
- reg rd_en ;
- reg sys_rst_n ;
- reg [1:0] cnt_baud ;
- reg full_reg0 ;
- reg full_reg1 ;
-
- wire empty ;
- wire full ;
- wire [7:0] wr_data_count ;
- wire [15:0] po_data ;
- wire [6:0] rd_data_count ;
-
- initial begin
- wr_clk = 1'b1;
- rd_clk = 1'b1;
- sys_rst_n <= 1'b0;
- #100;
- sys_rst_n <= 1'b1;
-
- end
-
- //wr_clk:模拟FIFO的写时钟,每10ns电平翻转一次,周期为 20ns,频率为50MHz
- always #10 wr_clk = ~wr_clk;
- //rd_clk:模拟FIFO的读时钟,每20ns电平翻转一次,周期为40ns,频率为25MHz
- always #20 rd_clk = ~rd_clk;
-
- //cnt_baud:计数从0到3的计数器,用于产生输入数据间的间隔
- always@(posedge wr_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- cnt_baud <= 2'b0;
- else if(cnt_baud == 2'd3)
- cnt_baud <= 2'b0;
- else
- cnt_baud <= cnt_baud + 1'b1;
- always@(posedge wr_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- pi_flag <= 1'b0;
- else if((cnt_baud == 2'd0) && (rd_en == 1'b0))
- pi_flag <= 1'b1;
- else
- pi_flag <= 1'b0;
-
- //pi_data:输入顶层模块的数据,要写入到FIFO中的数据
- always@(posedge wr_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- pi_data <= 8'b0;
- pi_data的值为0~255依次循环
- else if((pi_data == 8'd255) && (pi_flag == 1'b1))
- pi_data <= 8'b0;
- else if(pi_flag == 1'b1) //每当pi_flag有效时产生一个数据
- pi_data <= pi_data + 1'b1;
-
- //将同步于rd_clk时钟的写满标志信号full在rd_clk时钟下打两拍
- always@(posedge rd_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- begin
- full_reg0 <= 1'b0;
- full_reg1 <= 1'b0;
- end
- else
- begin
- full_reg0 <= full;
- full_reg1 <= full_reg0;//打两拍
- end
-
- //rd_en:FIFO读请求信号同步于rd_clk时钟
- always@(posedge rd_clk or negedge sys_rst_n)
- if(sys_rst_n == 1'b0)
- rd_en <= 1'b0;
- else if(full_reg1 == 1'b1)
- rd_en <= 1'b1;
- else if(empty == 1'b1)//当FIFO中的数据被读空时停止读取FIFO中的数据
- rd_en <= 1'b0;
-
- fifo fifo_inst
- (
- .wr_clk (wr_clk ),
- .pi_data (pi_data ),
- .pi_flag (pi_flag ),
- .rd_clk (rd_clk ),
- .rd_en (rd_en ),
- .po_data (po_data ),
- .empty (empty ),
- .full (full ),
- .rd_data_count(rd_data_count),
- .wr_data_count(wr_data_count)
- );
-
- endmodule
仿真结果:
整体波形变化:
数据输入部分:
数据输出部分:
由上可知读写位宽不同的FIFO,数据输入输出顺序是当输入位宽小于输出位宽时,先入存高位后入存低位。反之则,先入存低位后入存高位。
学完fifo,简单总结三点:
1. 合理控制 IP核大小(数据深度与位宽),避免资源浪费。
2. 注意利用好 FIFO 的关键信号,如读写时钟、读写使能、空满标志信号。
3. 写数据的总带宽一定要等于读数据的总带宽,否则一定会存在写满或读空的现象。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。