【实验任务】向FIFO中以50MHz的频率按顺序写入0~254,再从FIFO中以5Hz的频率按顺序读出0~254,输出到LED中点亮。
一、FIFO简介
FIFO全称为“First-In-First-Out”,是FPGA内部的存储数据的缓冲器,读写数据具有先入先出的特点,与数据结构中的“队列”有异曲同工之妙。
本博客例化紫光同创官方提供的FIFO ip核,实现数据的写入读出,烧录到PGL50H开发板,以LED灯指示数据的一次写入读出是否已完成。
例化FIFO ip核各参数如上。有几个特别重要的选项要注意一下:
① FIFO Type:可选择是同步FIFO(SYN_FIFO,读写时钟相同、读写复位引脚相同)还是异步FIFO(ASYN_FIFO,读写时钟不同、读写复位引脚不同)
② Address Width:地址宽度,即可以存多少个数据(图中设为8,即可以存放1111_1111(b) = 255(d)个数据)
Data Width:数据宽度,即数据最大是二进制的几位(图中设为8,即每个数据最大只能255)
如果勾选了Write/Read Port Use Same Data Width,那么写模块读模块将保持相同的Address Width和Data Width。
③ Almost Full Water Level:当写到某某位时,almost full信号拉高,提示“FIFO将被写满”
紫光同创的ip核标明了地址宽度设为8时,写入第252个数据会拉高almost full,仿真结果如下:
程序从0开始写起,每次自增1,所以当写入251(即第252位)时,almost_full拉高,还剩下252 253 254 没写。如果程序设定almost_full拉高的下一个时钟上升沿停止写入(250 -> 251 -> 0),那要延迟3个时钟。
④ Almost Empty Water Level:当写到某位时,almost empty信号拉高,提示“FIFO将被读空”
紫光同创的ip核表示地址宽度设为8时,读出倒数第5位(0 1 2 3 4,所以4是倒数第5位)数据时会拉高almost empty,仿真结果如下:
程序从0开始读起,每次自增1,所以当读出250时,还剩下251 252 253 254 没读,如果程序设定almost_empty拉高的下一个时钟上升沿停止写入(249 -> 250 -> 0),那要延迟4个时钟。
二、系统框架
如上图,虽然例化的ip核是“异步FIFO”,但是读时钟与写时钟均为系统时钟CLK,所以本实验最终结果仍然是“同步FIFO”。
实际上“Write_water_level”、“Write_full”、“Read_water_level”、“Read_empty”一般只发挥调试作用,不会应用到,所以这些信号不需要引入到两个控制模块中。
我们知道写模块以FIFO读完的 almost_empty 为写入的起始条件,读模块以FIFO写完的标志almost_empty为读出的起始条件,那我们要怎么知道FIFO已经读完或者写完了呢?
答:在“Write_almost_full”、“Read_almost_empty”信号拉高后,延时10拍。
防止FIFO 内部信号更新比实际的数据读/写操作有所延时而导致的时序紊乱,即等待到 FIFO 的空/满状态信号、数据计数信号等信号的更新完毕之后再进行 FIFO 写/读操作。
那如果写模块与读模块的时间频率不同,频率较快的打完10拍后,频率较慢的模块仍然没写完or读完怎么办呢?
答:将频率较慢的读/写模块打拍之后的almost_empty_sync / almost_full_sync信号输入到另一个模块当中去。
三、FIFO写控制模块设计
// 写在开头:以50MHz的频率写入FIFO
module fifo_write(
input wire clk,
input wire rst,
input wire almost_full,
input wire almost_empty,
output reg write_en,
output reg [7:0] data_write
);
parameter IDLE = 2'd0; // 等待FIFO数据"将要全部被读出"的状态
parameter WAIT = 2'd1; // 等待到FIFO数据"全部被读出"的状态
parameter WRITE = 2'd2; // 向FIFO发送数据的状态
reg [1:0] state;
reg [1:0] next_state;
reg [3:0] pause_count;
reg pause_flag;
// 延迟"将满"信号为 第252位 的打拍寄存器
reg almost_full_delay0;
reg almost_full_sync;
always @(posedge clk) begin
if(!rst) begin
almost_full_delay0 <= 1'b0;
almost_full_sync <= 1'b0;
end
else begin
almost_full_delay0 <= almost_full;
almost_full_sync <= almost_full_delay0; // 将满信号打两拍
end
end
// 以下为FIFO写控制状态机,收到"将空信号"后等待10拍完全读空FIFO(有延迟)
// 完全读空FIFO后,开始写入FIFO
// 收到"将满信号"后,不用管FIFO写入延迟,直接进入初始状态等待"将空信号"
always @(posedge clk) begin
if(!rst)
state <= IDLE;
else
state <= next_state;
end
always @(*) begin
if(!rst)
next_state <= 1'b0;
else
case (state)
IDLE : next_state <= (almost_empty)? WAIT : IDLE;
WAIT : next_state <= (pause_flag)? WRITE : WAIT;
WRITE : next_state <= (almost_full_sync)? IDLE : WRITE;
default : next_state <= IDLE;
endcase
end
// 各个状态下输出信号的变化
// 收到“将满信号”之后停止写入,直到收到“将空信号”
always @(posedge clk) begin
if(!rst) begin
data_write <= 8'd0;
write_en <= 1'b0;
pause_count<= 4'd0;
pause_flag <= 1'd0;
end
else if(state == IDLE) begin
write_en <= 1'b0;
data_write <= 8'd0;
end
else if(state == WAIT) begin
if(pause_count >= 4'd9) begin
pause_count <= 4'd0;
pause_flag <= 1'b1;
write_en <= 1'b1; //write_en在data_write+1之前拉高,从而保证data_write第0位可以顺利发出去
end
else begin
pause_count <= pause_count + 1'b1;
pause_flag <= 1'b0;
end
end
else if(state == WRITE) begin
write_en <= 1'b1;
data_write <= data_write + 1'b1;
end
else begin
data_write <= 8'd0;
write_en <= 1'b0;
pause_count<= 4'd0;
pause_flag <= 1'd0;
end
end
endmodule
四、FIFO读控制模块设计
// 写在开头:以5Hz的频率读出FIFO,仿真暂时使用5MHz
module fifo_read(
input wire clk,
input wire rst,
input wire almost_full,
input wire almost_empty,
input wire [7:0] data_read,
output reg read_en,
output wire almost_empty_output,
output wire [7:0] led_flag
);
parameter IDLE = 2'd0; // 等待FIFO数据"将要全部被读出"的状态
parameter WAIT = 2'd1; // 等待到FIFO数据"全部被读出"的状态
parameter READ = 2'd2; // 向FIFO发送数据的状态
reg [1:0] state;
reg [1:0] next_state;
reg [3:0] pause_count;
reg pause_flag;
// 延迟"将空"信号在第4位发出 的打拍寄存器
reg almost_empty_delay0;
reg almost_empty_delay1;
reg almost_empty_sync;
reg almost_empty_output_delay;
always @(posedge clk) begin
if(!rst) begin
almost_empty_delay0 <= 1'b0;
almost_empty_delay1 <= 1'b0;
almost_empty_sync <= 1'b0;
almost_empty_output_delay <= 1'b0;
end
else begin
almost_empty_delay0 <= almost_empty;
almost_empty_delay1 <= almost_empty_delay0;
almost_empty_sync <= almost_empty_delay1; // 将空信号打五拍
almost_empty_output_delay <= almost_empty_sync; // 再延时一拍,就可以在输出最后一位的同时告诉写模块“已经全部读完了”
end
end
// 以下为FIFO读控制状态机,收到"将满信号"后等待10拍完全写满FIFO(有延迟)
// 完全写满FIFO后,开始读出FIFO
// 收到"将空信号"后,不用管FIFO读出延迟,直接进入初始状态等待"将满信号"
always @(posedge clk) begin
if(!rst)
state <= IDLE;
else
state <= next_state;
end
always @(*) begin
if(!rst)
next_state <= 1'b0;
else
case (state)
IDLE : next_state <= (almost_full)? WAIT : IDLE;
WAIT : next_state <= (pause_flag)? READ : WAIT;
READ : next_state <= (almost_empty_sync)? IDLE : READ;
default : next_state <= IDLE;
endcase
end
// 各个状态下输出信号的变化
// 收到“将空信号”之后停止读出,直到收到“将满信号”
always @(posedge clk) begin
if(!rst) begin
read_en <= 1'b0;
pause_count<= 4'd0;
pause_flag <= 1'b0;
end
else if(state == IDLE) begin
read_en <= 1'b0;
end
else if(state == WAIT) begin
if(pause_count >= 4'd9) begin
pause_count <= 4'd0;
pause_flag <= 1'b1;
end
else begin
pause_count <= pause_count + 1'b1;
pause_flag <= 1'b0;
end
end
else if(state == READ) begin
read_en <= 1'b1;
end
else begin
read_en <= 1'b0;
pause_count<= 4'd0;
pause_flag <= 1'b0;
end
end
assign led_flag = data_read;
assign almost_empty_output = almost_empty_output_delay;
endmodule
五、FIFO读写TOP模块设计
TOP模块起着例化各子模块的作用
// 写在开头:1、写入FIFO时钟频率 50MHz ; 读出FIFO时钟频率 5Hz
// 2、模块fifo_write开始写的标志是:“FIFO空了”
// 模块fifo_read 开始读的标志是:“FIFO满了”
// 3、写模块在almost_empty到来时,延迟10拍再开始写入,
// 是因为almost_empty在剩下4个数时便拉高,延迟10拍后基本读完了可以开始写;读模块延时也是一样的道理
// 但是如果两个模块clk频率不一样呢?那么延时的工作就要交给频率低的模块执行。
// 4、根据2和3,如果读模块频率慢,那要将延时后的almost_empty输入到fifo_write
// 如果写模块频率慢,那要将延时后的almost_full 输入到fifo_read
module top(
input wire clk,
input wire rstn_out,
output wire [7:0] led_flag //指示灯,输出FIFO里面读出的东西
);
reg [23:0] count_5hz;
reg clk_5hz;
// FIFO信号
wire fifo_write_enable;
wire fifo_almost_full;
wire [7:0] fifo_data_write;
wire fifo_full;
wire [8:0] fifo_write_count;
wire fifo_read_enable;
wire fifo_almost_empty;
wire [7:0] fifo_data_read;
wire fifo_empty;
wire [8:0] fifo_read_count;
// 频率低的模块,延时后输出“将空/将满信号”
wire fifo_delay_almost;
// 分频器:50MHz --> 5Hz,仿真暂时使用5MHz
always @(posedge clk) begin
if(!rstn_out) begin
count_5hz <= 24'd0;
clk_5hz <= 1'b0;
end
// else if(count_5hz >= 24'd10_000_000) begin
// clk_5hz <= ~clk_5hz;
// count_5hz <= 24'd0;
// end
else if(count_5hz >= 24'd10) begin
clk_5hz <= ~clk_5hz;
count_5hz <= 24'd0;
end
else
count_5hz <= count_5hz + 1'b1;
end
fifo_generator_0 instance1 (
.wr_clk (clk ), // input
.wr_rst (~rstn_out ), // 高电平复位
.wr_en (fifo_write_enable ), // input
.wr_data (fifo_data_write ), // input [7:0]
.wr_full (fifo_full ), // output
.wr_water_level (fifo_write_count), // output [8:0]
.almost_full (fifo_almost_full ), // output
.rd_clk (clk_5hz ), // input
.rd_rst (~rstn_out ), // 高电平复位
.rd_en (fifo_read_enable ), // input
.rd_data (fifo_data_read ), // output [7:0]
.rd_empty (fifo_empty ), // output
.rd_water_level (fifo_read_count), // output [8:0]
.almost_empty (fifo_almost_empty ) // output
);
fifo_write instance2(
.clk (clk),
.rst (rstn_out),
.data_write (fifo_data_write),
.write_en (fifo_write_enable),
.almost_full (fifo_almost_full),
.almost_empty (fifo_delay_almost)
);
fifo_read instacne3(
.clk (clk_5hz),
.rst (rstn_out),
.data_read (fifo_data_read),
.read_en (fifo_read_enable),
.almost_empty (fifo_almost_empty),
.almost_full (fifo_almost_full),
.almost_empty_output(fifo_delay_almost),
.led_flag (led_flag)
);
endmodule
六、仿真分析
首先,以50MHz写入FIFO,将满信号延迟两拍得到almost_full_sync信号,进入IDLE状态机(state == 2'h0)的下一个时钟上升沿,掐断fifo_write_enable信号。
首先,以5MHz读出FIFO,将空信号延迟两拍得到almost_empty_sync信号,进入IDLE状态机(state == 2'h0)的下一个时钟上升沿,掐断fifo_read_enable信号。文章来源:https://www.toymoban.com/news/detail-558765.html
综合上面的仿真图形我们可以看到,data变为0和enable拉低是在同一个时钟沿进行的。所以什么时候不想写了就得马上拉低fifo_write_enable信号,什么时候不想读了就得马上拉低fifo_read_enable信号。文章来源地址https://www.toymoban.com/news/detail-558765.html
到了这里,关于FPGA学习笔记——FIFO读写的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!