FIFO求和实验

发布时间 2023-08-27 17:58:19作者: jasonlee~

第44章、FIFO求和实验

【理论】

【注】数据矩:5行(m) 4列(n)),对3行(x)求和

  1. 原数据矩阵m*n,m表示行数,n表示每行数据个数
  2. fifo深度要大于每行个数(显然)
  3. fifo个数为 n-1 个
  4. 求和后形成的结果矩阵 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 ;//系统时钟频率 50MHZ

localparam 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_cnt
4'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 ;
end

assign 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信号波形的设计与实现

  1. 行计数器(cnt_row)、列计数器(cnt_col):要实现3行数据的求和,需要对参与求和运算的行数 以及 每行数据个数 进行计数。
  2. cnt_col计数器初值为0,以输入数据标志信号pi_flag为约束条件进行计数,pi_flag信号每拉高一次,计数器加1,当cnt_row计数器计到最大值(计数器计数最大值为50-1=49),列计数器归0,开始下一行计数;
  3. 行计数器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:

  1. 输入时钟信号为系统时钟信号sys_clk,与串口接收模块的工作时钟相同;
  2. 数据写使能信号为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:

  1. 输入时钟信号与串口接收模块的工作时钟相同为系统时钟信号sys_clk;
  2. 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