学习FPGA的笔记,根据正点原子视频进行学习
开发板AX7020中本身并没有FIFO模块,其FIFO模块其实是通过block memory generator产生的
FIFO与RAM模块不同的是FIFO好像是一个管道,并没有对应的地址,其遵循先进先出的原则。其可作用在同步或者异步之间传输数据。
一、实验内容
创建FIFO模块并写入0-255再读出来
二、实验分析
首先我们需要调用IP_FIFO模块,再通过一个写入驱动模块以及一个读出驱动模块对FIFO进行驱动,由于本次实验进行的是异步FIFO实验(读和写用的不是一个时钟),因此我们需要一个锁相环(PLL)模块对时钟进行分频。最后我们需要一个顶层模块对以上四个模块进行调用。
三、实验步骤
1.IP_FIFO模块调用配置
直接在IP Catalog模块中搜索FIFO,其中有一个FIFO Generator,进入即为FIFO配置模块。Basic模块中可以选择不同的FIFO类型,前面几个带有Common的是同步FIFO,后面几个是异步的FIFO,具体不同点可以查看数据手册。
我们这里选用第四个Block产生的异步FIFO。
Native Ports中可以对FIFO的容量进行配置,其中包括宽度以及深度
Status Flags中可以设置Almost Flag,这两个选项勾选上,它表明是加入FIFO快满信号(实际上是,FIFO还有一位就满了或者还有一位就空了的时候这个引脚会拉高,在后面是很有用的)。
其余配置可以根据数据手册按照自己的需求进行配置。
2.IP_PLL配置
在IP Catalog中搜索clock,选择clock wzard选项进入配置
clocking options中可以设置MMCM或者PLL类型,MMCM比PLL功能稍微强大一点,能用 PLL解决的MMCM也行,下面一些配置我们默认。
output clock页面是配置输出时钟,可以设置输出时钟的条数,名称,要求的频率,相位,占空比,下面可以设置是否要reset,locked。
注意:FIFO、PLL的reset都是高电平有效即输入1复位在该页面可以修改,但AX7020的按键是输入0有效。
后面的页面保持默认。
3.分析FIFO时序图
以上是三个模块(FIFO)的输入输出,以及整个项目中四个模块之间的关系以及输入输出。
下面对写、读模块进行分析:
256位FIFO实际容量是255,因此写入数据位0-254
写模块:
clk_50m:时钟 50m
rst_n:复位
wr_rst_busy: 复位在忙信号,复位结束一段时间后拉低
empty:FIFO是否为空,通常写入几个数据之后,空信号才会由高变低
empty_d0 d1:打拍,(避免亚稳态的产生,常常采用打两拍的方法,类似于消抖)
fifo_wr_en: 写使能,在复位繁忙结束后即可进行写使能的拉高
fifo_wr_data:FIFO写入数据,注意定义256位的FIFO实际只有255位可以存储数据
almost_full:当还差一位满的时候进行拉高,通常用于停止写入
读模块:
clk_100m:时钟
rst_n:复位
full:满
fifo_rd_en:读使能信号
fifo_rd_data:读取数据、
almost_empty:几乎空
这里解释一下我们为什么需要“几乎满”、“几乎空了这两个信号”
我们使用满信号/几乎满信号来判断是否停止写操作(如果满了之后仍然进行写操作是无效的,而且会触发溢出标志)。如果我们使用满信号,则在写完254的周期的下一个时钟上升沿拉上满信号,因此在254下一个周期判断full是否是1,由于非阻塞赋值的原因,254周期full信号仍然是0,因此在下一个周期内检测到的full仍然是0。这样会导致有一位溢出。
如果使用快满信号,则在写入254位的周期时候快满信号拉高,虽然在本周期内无法检测到快满拉高,但下一周期就不会继续写入。
3.代码编写
3.1 写入模块
module fifo_wr(
(*mark_debug = "true"*) input clk_50M ,
(*mark_debug = "true"*) input rst ,
(*mark_debug = "true"*) input locked ,
(*mark_debug = "true"*) input empty ,
(*mark_debug = "true"*) input almost_full ,
(*mark_debug = "true"*) input wr_rst_busy ,
(*mark_debug = "true"*) output reg fifo_wr_en ,
(*mark_debug = "true"*) output reg [7:0] fifo_wr_data
);
(*mark_debug = "true"*) reg empty_1;
(*mark_debug = "true"*) reg empty_2; //实际上使用的是empty_2,相当于消抖
//要对异步信号进行打两拍的操作
always @(posedge clk_50M or negedge rst) begin
if(rst == 1'b0) begin
empty_1 <= 1'b0;
empty_2 <= 1'b0;
end
else begin
empty_1 <= empty ;
empty_2 <= empty_1;
end
end
//fifo_wr_en
always @(posedge clk_50M or negedge rst) begin
if(rst == 1'b0)
fifo_wr_en <= 1'b0;
else if (rst == 1'b1 && locked == 1'b1 && wr_rst_busy == 1'b0 )
if( empty_2 == 1'b1 )
fifo_wr_en <= 1'b1;
else begin
fifo_wr_en <= fifo_wr_en;
if(almost_full == 1'b1)
fifo_wr_en <= 1'b0;
end
// if( almost_full == 1'b1)
// fifo_wr_en <= 1'b0;
else
fifo_wr_en <= 1'b0;
end
//fifo_wr_data
always @(posedge clk_50M or negedge rst) begin
if (rst == 1'b0)
fifo_wr_data <= 8'b0;
else if( fifo_wr_en == 1'b1)
fifo_wr_data <= fifo_wr_data + 8'b1;
end
endmodule
(*mark_debug=="true"*)是后面调试的代码,可以忽略
需要注意的就是打两拍的这段代码是第一层接触,其实其意思也就是使用两个周期后的异步信号的值,防止不同时钟频率之间不稳定的情况。
3.2读出模块
module fifo_rd(
(*mark_debug = "true"*) input clk_100M ,
(*mark_debug = "true"*) input rst ,
(*mark_debug = "true"*) output reg fifo_rd_en ,
(*mark_debug = "true"*) input almost_empty,
(*mark_debug = "true"*) input full ,
(*mark_debug = "true"*) input rd_rst_busy ,
(*mark_debug = "true"*) input [7:0] fifo_rd_data,
(*mark_debug = "true"*) input locked
);
(*mark_debug = "true"*) reg full_1;
(*mark_debug = "true"*) reg full_2;
always @(posedge clk_100M or negedge rst) begin
if(rst == 1'b0) begin
full_1 <= 1'b0;
full_2 <= 1'b0;
end
else begin
full_1 <= full ;
full_2 <= full_1;
end
end
//fifo_rd_en
always @(posedge clk_100M or negedge rst) begin
if (rst == 1'b0)
fifo_rd_en <= 1'b0;
else if (rst == 1'b1 && locked == 1'b1 && rd_rst_busy == 1'b0)
if (full_2 == 1'b1)
fifo_rd_en <= 1'b1;
else begin
fifo_rd_en <= fifo_rd_en;
if(almost_empty == 1'b1)
fifo_rd_en <= 1'b0;
end
else
fifo_rd_en <= 1'b0 ;
end
endmodule
同样需要打两排的模块
3.3顶层封装模块
module ip_fifo(
input sys_clk,
input sys_rst
);
wire clk_50M ;
wire clk_100M ;
wire locked ;
wire [7:0] fifo_wr_data;
wire fifo_wr_en ;
wire fifo_rd_en ;
wire [7:0] fifo_rd_data ;
wire full ;
wire almost_full ;
wire empty ;
wire almost_empty ;
wire [7:0] rd_data_count;
wire [7:0] wr_data_count;
wire wr_rst_busy ;
wire rd_rst_busy ;
//pll
clk_wiz_0 u_clk_wiz_0
(
// Clock out ports
.clk_50M (clk_50M) , // output clk_50M
.clk_100M (clk_100M) , // output clk_100M
// Status and control signals
.reset (~sys_rst) , // input reset
.locked (locked) , // output locked
// Clock in ports
.clk_in1 (sys_clk)
);
//fifo
fifo_generator_0 u_fifo_generator_0 (
.rst (~sys_rst ), // input wire rst fifo 高电平有效
.wr_clk (clk_50M ), // input wire wr_clk
.rd_clk (clk_100M ), // input wire rd_clk
.din (fifo_wr_data ), // input wire [7 : 0] din
.wr_en (fifo_wr_en ), // input wire wr_en
.rd_en (fifo_rd_en ), // input wire rd_en
.dout (fifo_rd_data ), // output wire [7 : 0] dout
.full (full ), // output wire full
.almost_full (almost_full ), // output wire almost_full
.empty (empty ), // output wire empty
.almost_empty (almost_empty ), // output wire almost_empty
.rd_data_count (rd_data_count ), // output wire [7 : 0] rd_data_count
.wr_data_count (wr_data_count ), // output wire [7 : 0] wr_data_count
.wr_rst_busy (wr_rst_busy ), // output wire wr_rst_busy
.rd_rst_busy (rd_rst_busy ) // output wire rd_rst_busy
);
fifo_wr u_fifo_wr (
.clk_50M ( clk_50M ) ,
.rst ( sys_rst ) ,
.locked ( locked ) ,
.empty ( empty ) ,
.almost_full ( almost_full ) ,
.wr_rst_busy ( wr_rst_busy ) ,
.fifo_wr_en ( fifo_wr_en ) ,
.fifo_wr_data ( fifo_wr_data )
);
fifo_rd u_fifo_rd(
.clk_100M ( clk_100M ) ,
.rst ( sys_rst ) ,
.fifo_rd_en ( fifo_rd_en ) ,
.almost_empty ( almost_empty ) ,
.full ( full ) ,
.rd_rst_busy ( rd_rst_busy ) ,
.fifo_rd_data ( fifo_rd_data ) ,
.locked ( locked )
);
endmodule
对四个子模块调用,注意引脚别错了就行。
4.仿真验证
需要单独写仿真模块代码,本项目中对于整体(顶层模块)来说只有系统时钟、系统复位两个输入,因此仿真只需要对这两个信号进行模拟输入即可。
`timescale 1ns/1ns
module tb_ip_2port_ram();
parameter clk_period = 20;
reg sys_clk;
reg sys_rst;
//初始化复位
initial begin
sys_clk <= 1'b0;
sys_rst <= 1'b0;
#200
sys_rst <= 1'b1;
end
//初始化时钟
always #(clk_period/2) sys_clk =~sys_clk;
ip_fifo u_ip_fifo (
.sys_clk (sys_clk),
.sys_rst (sys_rst)
);
endmodule
(50M的时钟周期20ns)
直接在vivado中进行仿真即可
可以看到正常写入数据
254写入后停止写入数据
正常读出是数据
四、总结
1.FIFO的使用
2.PLL使用
3.异步模块之间需要打两排
4.PLL FIFO默认情况下1为复位信号(可修改)