FIFO求和实验
第44章、FIFO求和实验
【理论】
【注】数据矩:5行(m) 4列(n)),对3行(x)求和
- 原数据矩阵m*n,m表示行数,n表示每行数据个数
- fifo深度要大于每行个数(显然)
- fifo个数为 n-1 个
- 求和后形成的结果矩阵 p(行)*q(列),q=n,p=m -x+1(每个fifo要存储行的次数)
要完成 3行数据的SUM求和,需要调用 2个FIFO IP核。当数据开始输入时:
(1)将数据的第0行数据存储到 fifo1 中,将第1行数据存储到 fifo2 中;(存入数据:3行数据其中2行存入 fifo1 和 fifo2 )
(2)当数据的第2行的第0个数据输入的同时,读取写入fifo1中的的第0个数据和写入fifo2中的第0个数据,将三个数据求和,求和结果实时输出;(读出 ifo1、fifo2与第3行相加)
(3)在完成求和的同时,将读取的 fifo2 中的第0个数据写入 fifo1 中,fifo1读出的数据弃之不用,将输入的第2行的数据写入fifo2中,当第2行的最后一个数据输入,完成前三行的最后一个求和运算后,第0行的数据已读取完成,第1行的数据重新写入fifo1,第2行的数据写入fifo2;(将第3行数据存入fifo2,第2行数据存入fifo1,再进而与第4行相加)
(4)当第3行数据开始传入时,开始进行第1行、第2行和第3行的数据求和运算,如此循环,直到最后一个数据输入,完成求和运算。
【实战】
【实验目标】
(1)使用Matlab生成一个“.txt”文件,文件中包含 模拟求和 的数据;
(2)PC机通过 串口RS232 发送模块 将数据传给FPGA,使用 双fifo 实现 三行数据的FIFO求和;
(3)通过 串口RS232 接收模块 将求和后的数据回传给PC机,并通过串口助手打印出求和数据。
【实验要求】
*.txt 文件包含 2500个 数据,为 0-49的50次循环,模拟 50x50 数组。
【整体说明】
本实验过程包括4个模块:
系统上电后,使用 PC机 通过 串口助手 发送待求和数据给FPGA,FPGA通过 串口接收模块 接收待求和数据;
数据拼接 完成后传入 数据求和模块,
求和结果 通过串口数据发送模块 回传给PC机,使用串口助手查看求和结果。
【串口接收模块(uart_rx)】
module uart_rx( input sys_clk , input sys_rst_n , input rx ,
output reg [7:0] po_data , output reg po_flag
);
parameter UART_BPS = 'd9600 ,//数据传输波特率 9600
CLK_FREQ = 'd50_000_000 ;//系统时钟频率 50MHZlocalparam BAUD_CNT_MAX = CLK_FREQ / UART_BPS ;
reg rx_reg1 ;
reg rx_reg2 ;
reg rx_reg3 ;
reg [3:0] bit_cnt ;
wire bit_flag ;//将接收的rx信号打两拍,消除亚稳态。再打一拍用消除亚稳态后的rx_reg2、rx_reg3进行下降沿判断
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_reg1 <= 1'b1 ;
else
rx_reg1 <= rx ;
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_reg2 <= 1'b1 ;
else
rx_reg2 <= rx_reg1 ;
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_reg3 <= 1'b1 ;
else
rx_reg3 <= rx_reg2 ;
end//找到起始位下降沿,数据开始传输
reg nedge_flag ;
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
nedge_flag <= 1'b0 ;
else if((rx_reg31'b1)&&(rx_reg21'b0))
nedge_flag <= 1'b1 ;
else
nedge_flag <= 1'b0 ;
end
//rx_en:接收数据工作使能信号
reg rx_en ;
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rx_en <= 1'b0 ;
else if(nedge_flag1'b1)
rx_en <= 1'b1 ;
else if((bit_cnt4'd8)&&(bit_flag))
rx_en <= 1'b0 ;
else
rx_en <= rx_en ;
end//波特率 9600,1s(10^9ns)传输数据 (9600*1)bit,时钟周期 20ns,传输1bit数据需要ns:(10^9ns)/9600
// --> 传输1bit数据需要时钟周期个数:((10^9ns)/9600)/20ns = 5208个,计数 0-5297
reg [12:0] baud_cnt ;
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
baud_cnt <= 13'b0 ;
else if((rx_en==1'b1) && (baud_cnt == (BAUD_CNT_MAX - 1'b1)))
baud_cnt <= 13'b0 ;
else if(rx_en == 1'b1)
baud_cnt <= baud_cnt + 1'b1 ;
else
baud_cnt <= 13'd0 ;
endassign bit_flag = (baud_cnt == ((BAUD_CNT_MAX/2)-1)) ;//在数据传输中间点赋值,更加准确
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
bit_cnt <= 4'b0 ;
else if((bit_cnt4'd8)&&(bit_flag1'b1))
bit_cnt <= 4'b0 ;
else if( bit_flag==1'b1 )
bit_cnt <= bit_cnt + 1'b1 ;
else
bit_cnt <= bit_cnt ;
end//串行转并行,移位
reg [7:0] rx_data ;
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rx_data <= 8'b0 ;
else if((bit_cnt > 1'b0) && (bit_flag == 1'b1))
rx_data <= {rx_reg3,rx_data[7:1]};
end
//移位完成标志信号
reg rx_flag ;
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_flag <= 1'b0 ;
else if(rx_en==1'b1)
rx_flag <= 1'b1 ;
else rx_flag <= 1'b0 ;
end//输出信号,8bit并行数据
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
po_data <= 8'b0 ;
else if(rx_flag)
po_data <= rx_data ;
else
po_data <= 8'b0 ;
end
//输出完成标志信号
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
po_flag <= 1'b0 ;
else
po_flag <= rx_flag ;
end
endmodule
【串口发送模块(uart_tx)】
module uart_tx( input sys_clk , //系统时钟50MHz input sys_rst_n , //全局复位 input [7:0] pi_data , //模块输入的8bit数据 input pi_flag , //并行数据有效标志信号
output reg tx //串转并后的1bit数据
);
////
// Parameter and Internal Signal *******************//
////
//localparam define
parameter UART_BPS = 'd9600 ;//串口波特率
parameter CLK_FREQ = 'd50_000_000 ;//时钟频率
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS ;//发送1bit数据需要的5208个时钟周期
//reg define
reg [12:0] baud_cnt ;
reg bit_flag ;
reg [3:0] bit_cnt ;
reg tx_en ;
////
// Main Code ****************************//
//***********************************************//
//tx_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
tx_en <= 1'b0;
else if(pi_flag == 1'b1)
tx_en <= 1'b1;
else if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
tx_en <= 1'b0;
end//baud_cnt:波特率计数器计数,从0计数到5207
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
baud_cnt <= 13'b0;
else if((baud_cnt == BAUD_CNT_MAX - 1) || (tx_en == 1'b0))
baud_cnt <= 13'b0;
else if(tx_en == 1'b1)
baud_cnt <= baud_cnt + 1'b1;
end//bit_flag:当baud_cnt计数器计数到1时让bit_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == ((BAUD_CNT_MAX/2) - 1))
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
end//bit_cnt:数据位数个数计数,10个有效数据(含起始位和停止位)到来后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
bit_cnt <= 4'b0;
else if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
bit_cnt <= 4'b0;
else if(bit_flag == 1'b1)
bit_cnt <= bit_cnt + 1'b1;
end//tx:输出数据在满足rs232协议(起始位为0,停止位为1)的情况下一位一位输出
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
tx <= 1'b1; //空闲状态时为高电平
else if(bit_flag == 1'b1)
case(bit_cnt)
0 : tx <= 1'b0; //起始位,默认为0
1 : tx <= pi_data[0];
2 : tx <= pi_data[1];
3 : tx <= pi_data[2];
4 : tx <= pi_data[3];
5 : tx <= pi_data[4];
6 : tx <= pi_data[5];
7 : tx <= pi_data[6];
8 : tx <= pi_data[7];
9 : tx <= 1'b1; //停止位,默认为1
default : tx <= 1'b1;
endcase
end
endmodule
【FIFO求和模块】
数据求和模块的作用是,接收串口数据接收模块传来的待求和数据,计算出求和结果并输出给串口数据发送模块。
输入时钟为系统时钟sys_clk,频率50MHz;输入复位信号sys_rst_n,低电平有效;输入数据pi_data和数据标志信号pi_flag由串口数据接收模块传入,传入数据按照时序写入2个FIFO中,完成求和运算后,将求和后数据 po_sum和标志信号po_flag传出。
数据求和模块内部例化了两个FIFO,目的是缓存待求和数据。
模块整体波形图:
第一部分:列计数器cnt_col、行计数器cnt_row信号波形的设计与实现
- 行计数器(cnt_row)、列计数器(cnt_col):要实现3行数据的求和,需要对参与求和运算的行数 以及 每行数据个数 进行计数。
- cnt_col计数器初值为0,以输入数据标志信号pi_flag为约束条件进行计数,pi_flag信号每拉高一次,计数器加1,当cnt_row计数器计到最大值(计数器计数最大值为50-1=49),列计数器归0,开始下一行计数;
- 行计数器cnt_row 初值为0,列计数器计数到最大值 且 pi_flag信号有效时,行计数器加1,行计数器计到最大值(49),归0
第二部分:FIFO缓存 相关信号波形
(1)因为串口每次只输入 单字节数据,要想实现 多行数据求和,必须使用FIFO对输入数据进行缓存,本实验要实现3行数据的求和,需要使用两个FIFO进行数据缓存。在模块中实例化两个FIFO,分别为 fifo_data_inst1 和 fifo_data_inst2,两个FIFO的输入输出信号端口相同,输入端口有4路,输出端口1路,共5路信号。
(2)fifo_data_inst1:
- 输入时钟信号为系统时钟信号sys_clk,与串口接收模块的工作时钟相同;
- 数据写使能信号为wr_en1、写入数据为data_in1,当串口接收模块传入第0行数据时,即cnt_col=0且pi_flag=1时,wr_en1信号赋值为高电平,相同条件下, pi_data赋值给data_in1,将第0行的数据暂存到fifo_data_inst1中;当第1行数据输入,wr_en1信号赋值为低电平,data_in1无数据输入,因为第1行的数据要暂存到fifo_data_inst2中;自第2行数据开始传入到倒数第二行数据传输完成,wr_en1信号由dout_flag信号赋值,当rd_en和wr_en2信号均为高电平时,dout_flag信号赋值高电平,其他时刻均为低电平。当dout_flag有效时,将fifo_data_inst2的读出数据data_out2赋值给data_in1。
(3)fifo_data_inst2:
- 输入时钟信号与串口接收模块的工作时钟相同为系统时钟信号sys_clk;
- wr_en2 为数据写使能信号,data_in2为写入数据:自第1行数据开始输入到倒数第二行数据输入完成,wr_en2写使能信号由 pi_flag信号赋值,时序上滞后pi_flag信号1个时钟周期,wr_en2赋值为高电平,fifo_data_inst2写使能有效,其他时刻写使能无效,写使能信号wr_en2有效时,将传入的数据pi_data赋值给data_in2。
(4)rd_en:两FIFO共用的读使能信号,自第2行数据开始传入到最后一行数据传输完成, pi_flag信号赋值给读使能rd_en,时序上rd_en滞后pi_flag信号1个时钟周期,其他时刻rd_en信号始终保持低电平;data_out1数据输出受控于rd_en读使能信号,读使能有效,data_out1数据输出,否则保持之前状态,时序上data_out1滞后于rd_en读使能信号1个时钟周期。data_out2数据输出同样受控于rd_en读使能信号,读使能有效,data_out2数据输出,否者保持之前状态,时序上data_out2滞后于rd_en读使能信号1个时钟周期。
第三部分:数据输出相关信号
两FIFO共用的读使能信号rd_en有效时,从FIFO中分别读取两个待相加数据,两数据与此时输入的数据pi_data做求和运算,声明一个新的标志信号做这三个数据 求和运算的标志信号。
以读使能信号rd_en滞后一个时钟信号生成 求和运算标志信号po_flag_reg。当po_flag_reg信号为高电平时,将读出两FIFO的数据data_out1、data_out2与此时输入的pi_data做求和运算得出 求和结果po_sum并输出;同时要输出与po_sum信号匹配的数据标志信号po_flag,利用po_flag_reg信号滞后一个时钟周期生成po_flag信号并输出,生成的po_flag与与po_sum信号同步。
module fifo_sum_ctrl( input sys_clk , //频率为50MHz input sys_rst_n , //复位信号,低有效 input [7:0] pi_data , //rx传入的数据信号 input pi_flag , //rx传入的标志信号
output reg [7:0] po_sum , //求和运算后的信号
output reg po_flag //输出数据标志信号
);
////
// Parameter and Internal Signal *******************//
//******************//
//parameter define
parameter CNT_ROW_MAX = 3'd4 ,
CNT_COL_MAX = 3'd5 ; //数据矩阵 5*4 ,共5行,每行4个数据//wire define
wire [7:0] dout_fifo1 ; //fifo1数据输出
wire [7:0] dout_fifo2 ; //fifo2数据输出
//reg define
reg [2:0] cnt_row ; //行计数
reg [2:0] cnt_col ; //场计数
reg wr_en1 ; //fifo1写使能
reg wr_en2 ; //fifo2写使能
reg [7:0] din_fifo1 ; //fifo1写数据输入
reg [7:0] din_fifo2 ; //fifo2写数据输入
reg rd_en ; //fifo1、fifo2共用的读使能
reg sum_flag ; //控制fifo1,2-84行的写使能
// reg po_flag_reg ; //输出标志位缓存,rd_en延后一拍得到,控制计算po_sum////
// Main Code ****************************//
////
//1、行列计数器:cnt_col、cnt_row,计数一行数据个数以及行数****//
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
cnt_col <= 3'd0;
else if((cnt_col == (CNT_COL_MAX-1'b1)) && (pi_flag == 1'b1))
cnt_col <= 3'd0;
else if(pi_flag == 1'b1)
cnt_col <= cnt_col + 1'b1;
end
//cnt_row:行计数器,计数行数
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
cnt_row <= 3'd0;
else if((cnt_col == (CNT_COL_MAX-1'b1)) && (cnt_row == CNT_ROW_MAX-1'b1) && (pi_flag == 1'b1))
cnt_row <= 3'd0;
else if((cnt_col == (CNT_COL_MAX-1'b1)) && (pi_flag == 1'b1))
cnt_row <= cnt_row + 1'b1;
end//2、写使能信号及fifo输入:wr_en1、din_fifo1、wr_en2、din_fifo2*//
//wr_en1:fifo1写使能信号,高电平有效
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
wr_en1 <= 1'b0;
else if((cnt_row == 3'd0) && (pi_flag == 1'b1))//1行4个数据入,fifo1写使能拉高,将第一行数据写入fifo1
wr_en1 <= 1'b1; //第0行写入fifo1
else
wr_en1 <= sum_flag; //在第1行数据相加后,sum_flag拉高,数据输出后将第2、3行分别写入fifo1
end
//din_fifo1:fifo1数据输入
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
din_fifo1 <= 8'b0;
else if(wr_en1 == 1'b1)//情形1:第0行数据输入,暂存fifo1中
din_fifo1 <= pi_data;
else if(sum_flag == 1'b1) //情形2:第2行数据已经写入fifo2,当开始求和时,将fifo2中的数据写入fifo1
din_fifo1 <= dout_fifo2;//fifo2读出数据存入fifo1
else
din_fifo1 <= din_fifo1;
end//wr_en2:fifo2写使能信号,高电平有效
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
wr_en2 <= 1'b0;
else if((cnt_row >= 3'd1) && (cnt_row <= CNT_ROW_MAX - 1'b1) && (pi_flag == 1'b1))
wr_en2 <= 1'b1; //2-CNT_COL_MAX行写入fifo2
else
wr_en2 <= 1'b0;
end
//din_fifo2:fifo2数据输入
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
din_fifo2 <= 8'b0;
else if(wr_en2)
din_fifo2 <= pi_data;
else
din_fifo2 <= din_fifo2;
end//******************3、fifo读使能信号rd_en:在第2行数据写入,即fifo2写满后拉高*****************//
//rd_en:fifo1和fifo2的共用读使能信号,将两行数据读出
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
rd_en <= 1'b0;
else if((cnt_row >= 3'd2)&&(cnt_row <= CNT_ROW_MAX)&&(pi_flag == 1'b1))//
rd_en <= 1'b1;
else
rd_en <= 1'b0;
end
/【注】fifo在读使能信号rd_en的控制下读出数据,但数据会滞后rd_en信号1个时钟周期。
故要将读出的数据相加还要一个与数据对齐的时钟信号sum_flag */
//sum_flag:求和输出标志信号
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
sum_flag <= 0;
else
sum_flag <= rd_en ;//sum_flag信号对齐
end
// //po_flag_reg:输出标志位缓存,延后rd_en一拍,控制po_sum信号
// always@(posedge sys_clk or negedge sys_rst_n)begin
// if(sys_rst_n == 1'b0)
// po_flag_reg <= 1'b0;
// else
// po_flag_reg <= rd_en;
// end//4、输出po_sum及输出标志信号po_flag//
//po_flag:输出标志信号,延后sum_flag一拍,与po_sum同步输出
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
po_flag <= 1'b0;
else
po_flag <= sum_flag;
end
//po_sum:求和数据输出
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
po_sum <= 8'b0;
else if( sum_flag == 1'b1)
po_sum <= dout_fifo1 + dout_fifo2 + pi_data;
else
po_sum <= po_sum;
end
////
// Instantiation //
//*//
fifo fifo_inst_01 (
. clock ( sys_clk ),
. data ( din_fifo1 ),
. rdreq ( rd_en ),
. wrreq ( wr_en1 ),
. q ( dout_fifo1 )
);
fifo fifo_inst_02 (
. clock ( sys_clk ),
. data ( din_fifo2 ),
. rdreq ( rd_en ),
. wrreq ( wr_en2 ),
. q ( dout_fifo2 )
);
endmodule
【顶层模块】
module top_fifo_sum( input sys_clk , input sys_rst_n , input rx ,
output tx
);wire pi_flag ;
wire [7:0] pi_data ;
wire po_flag ;
wire [7:0] po_sum ;uart_rx uart_rx_inst01(
. sys_clk (sys_clk ),
. sys_rst_n (sys_rst_n),
. rx (rx ),. po_data (pi_data ), . po_flag (pi_flag )
);
fifo_sum_ctrl fifo_sum_ctrl_inst01(
. sys_clk (sys_clk ) , //频率为50MHz
. sys_rst_n (sys_rst_n) , //复位信号,低有效
. pi_data (pi_data ) , //rx传入的数据信号
. pi_flag (pi_flag ) , //rx传入的标志信号. po_sum (po_sum ) , //求和运算后的信号 . po_flag (po_flag ) //输出数据标志信号
);
uart_tx uart_tx_inst01(
. sys_clk (sys_clk ), //系统时钟50MHz
. sys_rst_n (sys_rst_n), //全局复位
. pi_data (po_sum ), //模块输入的8bit数据
. pi_flag (po_flag ), //并行数据有效标志信号. tx (tx ) //串转并后的1bit数据
);
endmodule