FPGA_FIFO深度与宽度的配置及验证
fifo是先进先出的存储器,在FPGA中应用于跨时钟域的情景,此次实验用于记载fifo的深度与宽度的配置及验证过程。
实验大致流程:
在fifo_wr模块中以wr_en时钟向FIFO存储器写入一组数,通过fifo_rd模块以rd_en时钟读出这组数据并向串口发送这组数据。
先用用Quartus II生成FIFO_IP核:
箭头1:设置FIFO的位宽,这里我们选择8bits。
箭头2:设置FIFO的深度,也就是能存放多少个指定位宽的数据,这里我们选择256words,这样设置 以后FIFO的容量大小为256个8bits。
箭头3:用于选择单时钟FIFO
箭头4:用于选择双时钟FIFO。
箭头5:选择不同的输出位宽(仅在双时钟时可选)。
此次实验我们选择双时钟FIFO,这时在箭头5处可以选择不同的输出位宽。这里我们采用默认方式输出数据与输入数据等宽。
跳到DCFIFO 2这里:
rdfull和wrfull:FIFO满的标记信号,高电平时表示FIFO已满,此时不能再进行写操作。
rdempty和wrempty:FIFO空的标记信号,高电平时表示FIFO已空,此时不能再进行读操作。
rdusedw[]和wrusedw[]:显示存储在FIFO中数据个数的信号。
Add an extra MSB to usedw ports:将rdusedw和wrusedw数据位宽增加1位,用于保护FIFO在写满时不会翻转到0。
Asynchronous clear:异步复位信号,用于清空FIFO。
这里我选择输出读空、读满、写空、写满等信号以备后面实验,至于rdusedw和wrusedw位宽加1、异步复位信号就没点,到这里FIFO的配置已经完成了,下面还需要一个UART串口发送的模块来配合实验。
//串口发送模块
module uart_send
(
input sys_clk , //50Mhz系统时钟
input sys_rst_n, //系统复位,低有效
input uart_en, //发送使能信号
input [7:0] uart_din, //待发送数据
input rdempty, //fifo读空标志
output reg uart_txd, //发送数据
output reg led //指示灯
);
`
parameter CLK_FREQ = 50000000; //系统时钟频率
parameter UART_BPS = 115200; //串口波特率
localparam BPS_CNT = CLK_FREQ/UART_BPS; //对系统时钟计数BPS_CNT次以得到指定波特率
reg [15:0] clk_cnt; //系统时钟计数器
reg [7:0] uart_data; //接收的数据
reg uart_done; //接收一帧数据完成标志信号
//reg define
reg uart_en_d0;
reg uart_en_d1;
reg [ 3:0] tx_cnt; //发送数据计数器
reg tx_flag; //发送过程标志信号
reg [ 7:0] tx_data; //寄存发送数据
reg [31:0] cnt; //时钟计数器
reg tx_delay;//wire define
wire en_flag;
wire wrreq ; // 写请求信号
wire [7:0] data ; // 写入FIFO的数据
wire wrempty ; // 写侧空信号
wire wrfull ; // 写侧满信号
wire wrusedw ; // 写侧FIFO中的数据量
wire rdreq ; // 读请求信号
wire [7:0] q ; // 从FIFO输出的数据
//wire rdempty ; // 读侧空信号
wire rdfull ; // 读侧满信号
wire rdusedw ; // 读侧FIFO中的数据量
//*****************************************************
//** main code
//*****************************************************
assign en_flag = (~uart_en_d1) & uart_en_d0; //& (~tx_delay);//消抖//按键消抖300ms
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt <= 0;
else if(cnt >= 31'd15_000_000) begin
led <= 0;
cnt <= 0;
tx_delay <= 0;
end
else if(tx_flag == 1) begin
tx_delay <= 1;
led <= 1;
end
else if(tx_delay == 1)
cnt <= cnt + 1'b1;
end//对发送使能信号uart_en延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_en_d0 <= 1'b0;
uart_en_d1 <= 1'b0;
end
else begin
uart_en_d0 <= uart_en;
uart_en_d1 <= uart_en_d0;
end
end
//当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
tx_flag <= 1'b0;
tx_data <= 8'd0;
end
else if (en_flag) begin //检测到发送使能上升沿
tx_flag <= 1'b1; //进入发送过程,标志位tx_flag拉高
tx_data <= uart_din; //寄存待发送的数据
end
else
if ((tx_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2))
begin //计数到停止位中间时,停止发送过程
tx_flag <= 1'b0; //发送过程结束,标志位tx_flag拉低
tx_data <= 8'd0;
end
else begin
tx_flag <= tx_flag;
tx_data <= tx_data;
end
end
//进入发送过程后,启动系统时钟计数器与发送数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
clk_cnt <= 16'd0;
tx_cnt <= 4'd0;
end
else if (tx_flag) begin //处于发送过程
if (clk_cnt < BPS_CNT - 1) begin
clk_cnt <= clk_cnt + 1'b1;
tx_cnt <= tx_cnt;
end
else begin
clk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零
tx_cnt <= tx_cnt + 1'b1; //此时发送数据计数器加1
end
end
else begin //发送过程结束
clk_cnt <= 16'd0;
tx_cnt <= 4'd0;
end
end
//根据发送数据计数器来给uart发送端口赋值
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
uart_txd <= 1'b1;
else if (tx_flag&&~rdempty)
case(tx_cnt)
4'd0: uart_txd <= 1'b0; //起始位
4'd1: uart_txd <= tx_data[0]; //数据位最低位
4'd2: uart_txd <= tx_data[1];
4'd3: uart_txd <= tx_data[2];
4'd4: uart_txd <= tx_data[3];
4'd5: uart_txd <= tx_data[4];
4'd6: uart_txd <= tx_data[5];
4'd7: uart_txd <= tx_data[6];
4'd8: uart_txd <= tx_data[7]; //数据位最高位
4'd9: uart_txd <= 1'b1; //停止位
default: ;
endcase
else if(tx_flag&&rdempty) //读区为空,输出8'hCC
case(tx_cnt)
4'd0: uart_txd <= 1'b0; //起始位
4'd1: uart_txd <= 1'b0; //数据位最低位
4'd2: uart_txd <= 1'b0;
4'd3: uart_txd <= 1'b1;
4'd4: uart_txd <= 1'b1;
4'd5: uart_txd <= 1'b0;
4'd6: uart_txd <= 1'b0;
4'd7: uart_txd <= 1'b1;
4'd8: uart_txd <= 1'b1; //数据位最高位
4'd9: uart_txd <= 1'b1; //停止位
default: ;
endcase
else
uart_txd <= 1'b1; //空闲时发送端口为高电平
end
endmodule
在这里说明一下,当读空信号成立时串口会发送:16进制数CC。另外还需要读写模块,如下:
//写FIFO模块
module fifo_wr(
//mudule clock
input clk , // 时钟信号
input rst_n , // 复位信号
//user interface
input wrempty, // 写空信号
input wrfull , // 写满信号
output reg [7:0] data , // 写入FIFO的数据
output reg wrreq // 写请求
);
//reg define
reg [1:0] flow_cnt; // 状态流转计数
reg [31:0] delay_a;
//*****************************************************
//** main code
//*****************************************************
//向FIFO中写入数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
wrreq <= 1'b0;
delay_a <= 1'b0;
data <= 1'b0;
flow_cnt <= 2'd0;
end
else if(delay_a == 31'd4) begin
delay_a <= 1'b0;
data <= data + 1'b1;
end
else begin
case(flow_cnt)
2'd0: begin
if(wrempty) begin //写空时,写请求拉高,跳到下一个状态
wrreq <= 1'b1;
flow_cnt <= flow_cnt + 1'b1;
end
else
flow_cnt <= flow_cnt;
end
2'd1: begin //写满时,写请求拉低,跳回上一个状态
if(wrfull) begin
wrreq <= 1'b0;
flow_cnt <= 2'd0;
end
else begin //没有写满的时候,写请求拉高,继续输入数据
wrreq <= 1'b1;
delay_a <= delay_a + 1'b1;
end
end
default: flow_cnt <= 2'd0;
endcase
end
end
endmodule
//读FIFO模块
module fifo_rd(
//system clock
input clk , // 时钟信号
input rst_n , // 复位信号(低有效)
//user interface
input [7:0] data , // 从FIFO输出的数据
input rdfull , // 读满信号
input rdempty, // 读空信号
output reg rdreq // 读请求
);
//reg define
reg [7:0] data_fifo; // 读取的FIFO数据
reg [1:0] flow_cnt ; // 状态流转计数
//*****************************************************
//** main code
//*****************************************************
//从FIFO中读取数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
rdreq <= 1'b0;
data_fifo <= 8'd0;
end
else begin
case(flow_cnt)
2'd0: begin
if(rdfull) begin
rdreq <= 1'b1;
flow_cnt <= flow_cnt + 1'b1;
end
else
flow_cnt <= flow_cnt;
end
2'd1: begin
if(rdempty) begin
rdreq <= 1'b0;
data_fifo <= 8'd0;
flow_cnt <= 2'd0;
end
else begin
rdreq <= 1'b1;
data_fifo <= data;
end
end
default: flow_cnt <= 2'd0;
endcase
end
end
endmodule
读写模块需要注意,我是以写满和写空为写操作的判断基准的,读操作的判断也是一样的,当然正常使用FIFO是不能这样做的,此次仅仅为了验证FIFO的宽度和深度。
以下是顶层模块,其中锁相环PLL并没有用到:
//fifo串口消息发送
module STM32_UART
(
input sys_clk , //50Mhz系统时钟
input sys_rst_n, //系统复位,低有效
input uart_en, //发送使能信号
output uart_txd, //发
input uart_rxd, //收
output reg led //指示灯
);
//reg define
reg wr_en;
reg rd_en;
reg [31:0] cnt;
reg [31:0] cnt_rd;
reg [31:0] cnt_led/* synthesis preserve */;
reg [7:0] data;
//wire define
wire [7:0] uart_data_w ; // 接收到的数据
wire wrreq ; // 写请求信号
//wire [7:0] data ; // 写入FIFO的数据
wire wrempty ; // 写侧空信号
wire wrfull ; // 写侧满信号
wire wrusedw ; // 写侧FIFO中的数据量
wire rdreq ; // 读请求信号
wire [3:0] q ; // 从FIFO输出的数据
wire rdempty ; // 读侧空信号
wire rdfull ; // 读侧满信号
wire rdusedw ; // 读侧FIFO中的数据量
wire clk_0/*synthesis keep*/;
wire clk_1/*synthesis keep*/;
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
led <= 1'b0;
cnt_led <= 1'b0;
end
else if(cnt_led == 5-1) begin
cnt_led <= 1'b0;
led <= ~led;
end
else begin
cnt_led <= cnt_led + 1'b1;
led <= led;
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
wr_en <= 1'b0;
cnt <= 1'b0;
end
else if(cnt == 31'd1_000_000) begin
cnt <= 1'b0;
wr_en <= 1'b1;
end
else begin
cnt <= cnt + 1'b1;
wr_en <= 1'b0;
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
rd_en <= 1'b0;
cnt_rd <= 1'b0;
end
else if(cnt_rd == 31'd2_000_000) begin
cnt_rd <= 1'b0;
rd_en <= 1'b1;
end
else begin
cnt_rd <= cnt_rd + 1'b1;
rd_en <= 1'b0;
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
data <= 1'b0;
else if(q != 0&&rdreq)
data <= q;
else if(rdreq==0)
data <= 8'hee;
else
data <= 8'hff;
end
uart_send u_uart_send(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_en (wr_en), //发送使能信号
.rdempty (rdempty), //fifo读空信号
.uart_din (data), //待发送数据
.uart_txd (uart_txd), //发送数据
);
//锁相环
pll_clk u_pll_clk(
.areset (~sys_rst_n ), //锁相环高电平复位,所以复位信号取反
.inclk0 (sys_clk ),
.c0 (clk_0 ),
.c1 (clk_1),
.locked (locked )
);
//例化FIFO模块
fifo u_fifo(
.wrclk ( wr_en ), // 写时钟
.wrreq ( wrreq ), // 写请求
.data ( uart_data_w ), // 写入FIFO的数据
.wrempty ( wrempty ), // 写空信号
.wrfull ( wrfull ), // 写满信号
.wrusedw ( wrusedw ), // 写侧数据量
.rdclk ( rd_en ), // 读时钟
.rdreq ( rdreq ), // 读请求
.q ( q ), // 从FIFO输出的数据
.rdempty ( rdempty ), // 读空信号
.rdfull ( rdfull ), // 读满信号
.rdusedw ( rdusedw ) // 读侧数据量
);
//例化写FIFO模块
fifo_wr u_fifo_wr(
.clk (wr_en ), // 写时钟
.rst_n (sys_rst_n), // 复位信号
.wrreq (wrreq ), // 写请求
.data (uart_data_w ), // 写入FIFO的数据
.wrempty (wrempty ), // 写空信号
.wrfull (wrfull ) // 写满信号
);
//例化读FIFO模块
fifo_rd u_fifo_rd(
.clk (rd_en ), // 读时钟
.rst_n (sys_rst_n), // 复位信号
.rdreq (rdreq ), // 读请求
.data (q ), // 从FIFO输出的数据
.rdempty (rdempty ), // 读空信号
.rdfull (rdfull ) // 读满信号
);
endmodule
顶层模块对读出的数据做了简单处理,读出数据为0时输出为FF,读请求为0时输出EE。
16进制显示串口打印结果如下:
下面贴个工程连接:
https://download.csdn.net/download/weixin_44007438/12897761
感兴趣的可以试试啦,有帮助的可以打个赏啦