要实现 FIFO
求和,
FIFO IP
核必不可少,需要用它用来做求和数据缓存。FIFO
是存储器的一种,满足先进先出原则,前面对它已经有了详细介绍,大家可自行翻阅我前面的博客。
要完成 3
行数据的 FIFO
求和,需要调用
2
个
FIFO IP
核,当数据开始输入时,将数据的第 0
行数据存储到
fifo1
中,将第
1
行数据存储到
fifo2
中,当数据的第
2
行的第
0
个数据输入的同时,读取写入 fifo1
中的的第
0
个数据和写入
fifo2
中的第
0
个数据,将三个数据求和,求和结果实时输出,在完成求和的同时,将读取的 fifo2
中的第
0
个数据写入
fifo1 中,fifo1
读出的数据弃之不用,将输入的第
2
行的数据写入
fifo2
中,当第
2
行的最后一个数据输入,完成前三行的最后一个求和运算后,第 0
行的数据已读取完成,第
1
行的数据重新写入 fifo1
,第
2
行的数据写入
fifo2
,当第
3
行数据开始传入时,开始进行第
1
行、第 2 行和第
3
行的数据求和运算,如此循环,直到最后一个数据输入,完成求和运算。流程示意图具体见图 1 。
图 1 求和运算流程图
FIFO 求和实验工程的整体框架如图 2 所示
图 2 实验工程整体框架
系统上电后,使用 PC 机通过串口助手发送待求和数据给 FPGA,FPGA 通过串口接收模块接收待求和数据,数据拼接完成后传入数据求和模块,经过求和运算后的数据结果通过串口数据发送模块回传给 PC 机,使用串口助手查看求和结果。
下面进行求和模块的编写说明:
图 3 FIFO 求和运算框架
由模块框图 3 可知,数据求和模块包含 4
路输入、
2
路输出,共
6
路信号。输入输出信号简介可见如下表格:
信号 | 位宽( bit ) | 输入输出 | 功能 |
sys_clk | 1 | input | 工作时钟,一般50MHz |
sys_rst_n | 1 | input | 复位信号 |
pi_flag | 1 | input | 输入数据标志信号 |
pi_data | 8 | input | 输入待求和数据 |
po_flag | 1 | output | 输出数据标志信号 |
po_sum | 8 | output | 输出求和后数据 |
下面是整体波形图配置:
图 4 数据求和模块波形图
第一部分:行计数器
cnt_row
、列计数器
cnt_col
信号波形的设计与实现
本实验是要实现 3
行数据的求和,那么需要对参与求和运算的每行数据个数进行计数,同样也需要对参与求和运算的各行进行计数,所以我们需要声明两个计数器行计数器cnt_row、列计数器 cnt_col
。
变量 cnt_row
是行计数器,计数每行数据的个数,我们可以以输入数据标志信号 pi_flag 为约束条件进行计数。
cnt_row
计数器初值为
0
,
pi_flag
信号每拉高一次,计数器加 1,当
cnt_row
计数器计到最大值
(
一行数据个数减
1
,本实验一行数据
50
个,计数器计数最大值为 50-1=49)
,行计数器归
0
,开始下一行计数;
cnt_col 是列计数器,对输入数据进行列计数
(
计数行个数
)
,计数器初值为
0
,行计数器计数到最大值且 pi_flag
信号有效时,列计数器加
1
,列计数器计到最大值
(
行个数减
1
,本实验数据共有 50
行,计数器计数最大值为
50-1=49)
,列计数器归
0
。两计数器计数器信号波形如下。
图 5 cnt_row, cnt_col 信号波形图
第二部分:FIFO
缓存相关信号波形的设计与实现
因为串口每次只输入单字节数据,要想实现多行数据求和,必须要使用 FIFO
对输入数据进行缓存,本实验要实现 3
行数据的求和,需要使用两个
FIFO
进行数据缓存。那么 FIFO 的相关信号的波形就需要设计一下了。
我们在模块中实例化两个 FIFO
,分别为
fifo_data_inst1
和
fifo_data_inst2
,接下来对两个 FIFO
的相关信号进行详细说明。
两个 FIFO
的输入输出信号端口相同,输入端口有
4
路,输出端口
1
路,共
5
路信号。
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
。
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
。
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
个时钟周期。
图 6 FIFO 缓存相关信号波形图
第三部分:数据输出相关信号波形的设计与实现
两 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 与与
op_sum
信号同步。上述各信号波形如下图。
图 7 输出数据相关信号波形图
下面进行代码的编写:
module fifo_sum_ctrl
(
input wire sys_clk , //时钟信号
input wire sys_rst_n , //复位信号
input wire [7:0] pi_data , //RX传入数据信号
input wire pi_flag , //RX传入标志信号
output reg [7:0] po_sum , //求和运算后的信号
output reg po_flag //输出数据标志信号
);
//parameter define
parameter CNT_ROW_MAX = 7'd4 , //行计数最大值
CNT_COL_MAX = 7'd5 ; //列计数最大值
//wire define
wire [7:0] data_out1 ; //fifo1数据输出
wire [7:0] data_out2 ; //fifo2数据输出
//reg define
reg [6:0] cnt_row ; //行计数
reg [6:0] cnt_col ; //列计数
reg wr_en1 ; //fifo1写使能
reg wr_en2 ; //fifo2写使能
reg [7:0] data_in1 ; //fifo1写数据输入
reg [7:0] data_in2 ; //fifo2写数据输入
reg rd_en ; //fifo1.fifo2共用的读使能
reg dout_flag ; //控制fifo1,2——84行的写使能
reg po_flag_reg ; //输出标志位缓存,rd_en延后一拍得到
//cnt_row:行计数器,计数一行数据个数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_row <= 7'd0 ;
else if((cnt_row == CNT_ROW_MAX) && (pi_flag == 1'b1))
cnt_row <= 7'd0 ;
else if(pi_flag == 1'b1)
cnt_row <= cnt_row + 1'b1 ;
//cnt_col:列计数器,计数一列数据个数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_col <= 7'd0 ;
else if((cnt_row == CNT_ROW_MAX) && (pi_flag == 1'b1) && (cnt_col == CNT_COL_MAX))
cnt_col <= 7'd0 ;
else if((cnt_row == CNT_ROW_MAX) && (pi_flag == 1'b1))
cnt_col <= cnt_col + 1'b1 ;
//wr_en1:fifo1写使能信号,高电平有效
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wr_en1 <= 1'b0 ;
else if((cnt_col == 7'd0) && (pi_flag == 1'b1))
wr_en1 <= 1'b1 ; //第0行写入fifo1
else
wr_en1 <= dout_flag ; //2-84行写入fifo1
//wr_en2:fifo2写使能信号,高电平有效
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wr_en2 <= 1'b0 ;
else if((cnt_col >= 7'd1) && (pi_flag == 1'b1))
wr_en2 <= 1'b1 ; //2——CNT_COL_MAX行写入fifo2
else
wr_en2 <= 1'b0 ;
//data_in1:fifo1数据输入
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_in1 <= 8'b0 ;
else if((pi_flag == 1'b1) && (cnt_col == 7'd0))
data_in1 <= pi_data ; //第0行数据暂存fifo1中
else if(dout_flag == 1'b1)
data_in1 <= data_out2 ; //第2-CNT_COL_MAX-1行时,fifo2读出数据存入fifo1
else
data_in1 <= data_in1 ;
//data_in2:fifo2数据输入
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_in2 <= 8'b0 ;
else if((pi_flag == 1'b1) && (cnt_col >= 7'd1) && (cnt_col <= (CNT_COL_MAX - 1'b1)))
data_in2 <= pi_data ;
else
data_in2 <= data_in2 ;
//rd_en:fifo1和fifo2的共用读使能信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_en <= 1'b0 ;
else if((pi_flag == 1'b1) && (cnt_col >= 7'd2) && (cnt_col <= CNT_COL_MAX))
rd_en <= 1'b1 ;
else
rd_en <= 1'b0 ;
//dout_flag:控制2——CNT_COL_MAX-1行wr_en1信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
dout_flag <= 1'b0 ;
else if((wr_en2 == 1'b1) && (rd_en == 1'b1))
dout_flag <= 1'b1 ;
else
dout_flag <= 1'b0 ;
//po_flag_reg:输出标志位缓存,延后rd_en一拍,控制po_sum信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_flag_reg <= 1'b0 ;
else if(rd_en == 1'b1)
po_flag_reg <= 1'b1 ;
else
po_flag_reg <= 1'b0 ;
//po_flag:输出标志信号,延后输出标志位缓存一拍,与po_sum同步输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_flag <= 1'b0 ;
else
po_flag <= po_flag_reg ;
//po_sum:求和数据输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_sum <= 8'b0 ;
else if(po_flag_reg == 1'b1)
po_sum <= data_out1 + data_out2 + pi_data ;
else
po_sum <= po_sum ;
//FIFO IP 核的调用
fifo_data fifo_data_inst1
(
.clock (sys_clk ), //input clock
.data (data_in1 ), //input [7:0] data
.wrreq (wr_en1 ), //input wrreq
.rdreq (rd_en ), //input rdreq
.q (data_out1 ) //output [7:0] q
);
fifo_data fifo_data_inst2
(
.clock (sys_clk ), //input clock
.data (data_in2 ), //input [7:0] data
.wrreq (wr_en2 ), //input wrreq
.rdreq (rd_en ), //input rdreq
.q (data_out2 ) //output [7:0] q
);
endmodule
信号发送和接收模块这个不再赘述,大家可根据自己以前写的 RS232 模块和上述求和模块一起编写到顶层模块中,RTL视图如图 8 所示:
图 8 RTL视图
将程序下载到开发板,这里输入五行四列的数据,通过串口调试助手发送并接收,如图 9 所示:
图 9 串口助手收发数据
与我们的计算结果相符,实验结束。
图 10 计算结果