前言
我們在上一章完成了UART串口通信的收發(fā)模塊,這一章我們將FIFO引入進(jìn)來,使用FIFO進(jìn)行緩存數(shù)據(jù),來連接串口通信的收發(fā)模塊
一丶FIFO介紹
1.什么是FIFO?
FIFO即First In First Out,是一種先進(jìn)先出數(shù)據(jù)存儲、緩沖器,我們知道一般的存儲器是用外部的讀寫地址來進(jìn)行讀寫,而FIFO這種存儲器的結(jié)構(gòu)并不需要外部的讀寫地址而是通過自動的加一操作來控制讀寫,這也就決定了FIFO只能順序的讀寫數(shù)據(jù)
2.FIFO分類
同步FIFO
讀和寫應(yīng)用同一個時鐘。它的作用一般是做交互數(shù)據(jù)的一個緩沖,也就是說它的主要作用就是一個buffer1。
異步FIFO
讀寫應(yīng)用不同的時鐘,它有兩個主要的作用,一個是實現(xiàn)數(shù)據(jù)在不同時鐘域進(jìn)行傳遞,另一個作用就是實現(xiàn)不同數(shù)據(jù)寬度的數(shù)據(jù)接口。
3.FIFO主要參數(shù)
同步FIFO和異步FIFO略有不同,下面的參數(shù)適用于兩者。
寬度
,用參數(shù)FIFO_data_size表示,也就是FIFO存儲的數(shù)據(jù)寬度;深度
,用參數(shù)FIFO_addr_size表示,也就是地址的大小,也就是說能存儲多少個數(shù)據(jù);滿標(biāo)志
,full,當(dāng)FIFO中的數(shù)據(jù)滿了以后將不再能進(jìn)行數(shù)據(jù)的寫入;空標(biāo)志
,empty,當(dāng)FIFO為空的時候?qū)⒉荒苓M(jìn)行數(shù)據(jù)的讀出;寫地址
,w_addr,由自動加一生成,將數(shù)據(jù)寫入該地址;讀地址
,r_addr,由自動加一生成,將該地址上的數(shù)據(jù)讀出;
同步FIFO和異步FIFO的最主要的不同就體現(xiàn)在空滿標(biāo)志產(chǎn)生的方式上,由此引出兩者一些不同的參數(shù)。
同步FIFO
- 時鐘,clk,rst,讀寫應(yīng)用同一個時鐘;
- 計數(shù)器,count,用計數(shù)器來進(jìn)行空滿標(biāo)志的判斷;
異步FIFO
- 時鐘,clk_w,rst_w,clk_r,rst_r,讀寫應(yīng)用不同的時鐘;
- 指針,w_pointer_gray,r_pointer_gray,用指針來判斷空滿標(biāo)識;
- 同步指針,w_pointer_gray_sync,r_pointer_gray_sync,指針的同步操作,用來做對比產(chǎn)生空滿標(biāo)志符;
4.測試
首先配置IP核
設(shè)置路徑,我們一般會在工程目錄下創(chuàng)建一個文件夾 ip 用來存放IP核文件
配置參數(shù)
正常模式與前顯模式:
區(qū)別:正常模式,輸出數(shù)據(jù)與讀請求信號差一個時鐘周期;前顯模式,將數(shù)據(jù)放于數(shù)據(jù)線上,在讀請求信號拉高時,在下一個時鐘周期,輸出FIFO中的第二個數(shù)據(jù)。
最后這樣就成功引入FIFO了
5.仿真
調(diào)用ip核
module control (
input clk ,
input rst_n ,
input [7:0] data ,
input rdreq ,
input wrreq ,
output empty ,
output full ,
output [7:0] q ,
output [7:0] usedw
);
fifo fifo_inst (
.aclr ( ~rst_n ), //復(fù)位信號取反
.clock ( clk ), //系統(tǒng)時鐘
.data ( data ), //寫入數(shù)據(jù)
.rdreq ( rdreq ), //讀使能
.wrreq ( wrreq ), //寫使能
.empty ( empty ), //fifo為空信號
.full ( full ), //fifo存滿信號
.q ( q ), //讀出數(shù)據(jù)
.usedw ( usedw ) //可用數(shù)據(jù)量
);
endmodule //control
testbench編寫
`timescale 1ns/1ps
module tb_control ();
reg clk ;
reg rst_n ;
reg [7:0] data ;
reg rdreq ;
reg wrreq ;
wire empty ;
wire full ;
wire [7:0] q ;
wire [7:0] usedw ;
control control(
.clk (clk ) ,
.rst_n (rst_n ) ,
.data (data ) ,
.rdreq (rdreq ) ,
.wrreq (wrreq ) ,
.empty (empty ) ,
.full (full ) ,
.q (q ) ,
.usedw (usedw )
);
parameter CYCLE = 20;
always #(CYCLE/2) clk=~clk;
integer i=0,j=0;
initial begin
clk=1;
rst_n=1;
data=0;
#200.1;
rst_n=0; //復(fù)位
rdreq=0;
wrreq=0;
#(CYCLE*10);
rst_n=1;
#(CYCLE*10)
//wrreq 50M
for(i=0;i<256;i=i+1)begin //因為我們的數(shù)據(jù)深度設(shè)置的是256,所以這里寫進(jìn)去256個數(shù)據(jù)
wrreq = 1'b1;//寫使能
data = {$random};
#CYCLE;
end
wrreq = 1'b0;//寫完拉低
#(CYCLE*5);
//rdreq 50M
for(j=0;j<256;j=j+1)begin
rdreq = 1'b1;//讀使能
#CYCLE;
end
rdreq = 1'b0;
#(CYCLE*10);
$stop;
end
endmodule //tb_control
寫數(shù)據(jù):
讀數(shù)據(jù):
二丶UART引入FIFO
思路:
首先我們將整個項目分為4個模塊
uart_rx:
接收模塊- - -從上位機接收數(shù)據(jù),然后將數(shù)據(jù)發(fā)送給control模塊uart_tx:
發(fā)送模塊- - -從control模塊接收數(shù)據(jù),然后發(fā)送給上位機control:
FIFO緩存模塊- - -緩存uart_rx接收的數(shù)據(jù)并輸出給uart_txtop:
頂層模塊
1.模塊原理圖
其中發(fā)送模塊uart_tx增加了一個ready輸出信號,因為發(fā)送模塊每434個周期發(fā)送一位數(shù)據(jù),為了防止FIFO不停的輸出數(shù)據(jù)給發(fā)送模塊,使用ready信號控制FIFO輸出數(shù)據(jù)
2.代碼設(shè)計
由于只改動了發(fā)送模塊和新增了control模塊,這里只展示這兩部分,源碼見文章末尾
control:
module control (
input clk ,
input rst_n ,
input [7:0] dout ,
input dout_vld ,
input ready ,
output [7:0] din ,
output din_vld
);
wire [7:0] data ;
wire rdreq;
wire wrreq;
wire empty;
wire full ;
wire [7:0] q ;
wire [7:0] usedw;
reg flag ;
assign data=dout;
assign wrreq=dout_vld&&~full;
assign din=q;
//flag
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
flag<=0;
end
else if(usedw>7) begin //存滿8個字節(jié)拉高flag
flag<=1;
end
else if (empty) begin
flag<=0;
end
end
assign rdreq=flag&&ready&&~empty;
assign din_vld=rdreq; //每次將din_vld拉高一個周期,輸出一字節(jié)數(shù)據(jù)
fifo fifo_inst (
.aclr ( ~rst_n ), //復(fù)位信號取反
.clock ( clk ), //系統(tǒng)時鐘
.data ( data ), //寫入數(shù)據(jù)
.rdreq ( rdreq ), //讀使能
.wrreq ( wrreq ), //寫使能
.empty ( empty ), //fifo為空信號
.full ( full ), //fifo存滿信號
.q ( q ), //讀出數(shù)據(jù)
.usedw ( usedw ) //可用數(shù)據(jù)量
);
endmodule //control
uart_tx:
module uart_tx (
input wire clk,
input wire rst_n,
input wire [7:0] din,
input wire din_vld,
output reg tx,
output reg ready
);
//定義一個寄存器來鎖存 din_vld 時的din
reg [9:0] data;
//波特率計數(shù)器
reg [8:0] cnt_bps;
wire add_cnt_bps;
wire end_cnt_bps;
//比特計數(shù)器
reg [4:0] cnt_bit;
wire add_cnt_bit;
wire end_cnt_bit;
reg flag; //計數(shù)器開啟標(biāo)志位
parameter BPS_115200=434; //發(fā)送一bit數(shù)據(jù)需要的周期數(shù)
//data
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data<=0;
end
else if(din_vld) begin
data<={1'b1,din,1'b0}; //拼接起始位和停止位
end
else
data<=data;
end
//發(fā)送數(shù)據(jù) tx
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx<=1'b1;
end
else if(cnt_bps==1) begin
tx<=data[cnt_bit];
end
end
//flag
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
flag<=0;
end
else if(din_vld) begin
flag<=1;
end
else if(end_cnt_bit) begin //發(fā)送完成關(guān)閉計數(shù)器
flag<=0;
end
else
flag<=flag;
end
//ready
always @(*) begin
if (!rst_n) begin
ready<=1;
end
else if(flag) begin
ready<=0;
end
else begin
ready<=1;
end
end
//cnt_bps
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_bps<=0;
end
else if(add_cnt_bps) begin
if (end_cnt_bps) begin
cnt_bps<=0;
end
else
cnt_bps<=cnt_bps+1;
end
end
assign add_cnt_bps=flag;
assign end_cnt_bps=add_cnt_bps&&cnt_bps==BPS_115200-1;
//cnt_bit
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_bit<=0;
end
else if(add_cnt_bit) begin
if (end_cnt_bit) begin
cnt_bit<=0;
end
else
cnt_bit<=cnt_bit+1;
end
end
assign add_cnt_bit=end_cnt_bps;
assign end_cnt_bit=add_cnt_bit&&cnt_bit==9;
endmodule //uart_tx
3.仿真與分析
testbench:
`timescale 1ns/1ps
module tb_uart ();
reg clk;
reg rst_n;
reg [7:0] din;
reg din_vld;
wire tx_r; //用來連接上位機的tx和從機的rx
wire rx;
parameter CYCLE=20;
//例化從機(頂層模塊,包含了一個uart_rx和一個uart_tx)
uart uart(
.clk (clk),
.rst_n (rst_n),
.rx (tx_r), //接收
.tx (tx) //發(fā)送
);
//例化上位機(用來給從機發(fā)送數(shù)據(jù))
uart_tx uart_tx(
.clk (clk),
.rst_n (rst_n),
.din (din),
.din_vld (din_vld),
.tx (tx_r)
);
always #(CYCLE/2) clk=~clk;
initial begin
clk=1;
rst_n=1;
#200;
rst_n=0;
din_vld=0;
#(CYCLE*10);
rst_n=1;
send(8'h11);
send(8'h22);
send(8'h33);
send(8'h44);
send(8'h55);
send(8'h66);
send(8'h77);
send(8'h88);
#2000000;
$stop;
end
task send;
input [7:0] send_data;
begin
din=send_data;
din_vld=1;
#CYCLE;
din_vld=0;
#(CYCLE*434*22);
end
endtask
endmodule //tb_uart_tx
分析:
1.上位機發(fā)送數(shù)據(jù)到FPGA之后由FPGA的接收模塊將數(shù)據(jù)dout
和數(shù)據(jù)有效信號dout_vld
輸出給FIFO緩存
2.dout_vld
作為寫使能信號,在寫使能開啟的時候存儲dout
3.在FIFO中存儲的數(shù)據(jù)大于7個
的時候開啟讀使能,因為FIFO模式設(shè)置的前顯模式,所以在讀使能生效前,第一位數(shù)據(jù)就有效了,也就是時序圖中的q信號:8’h11
然后來看發(fā)送數(shù)據(jù):
箭頭處,din_vld
拉高一個周期,目的是為了在我們發(fā)送完一幀數(shù)據(jù)之前,只鎖存一次數(shù)據(jù),保證發(fā)送一幀數(shù)據(jù)期間數(shù)據(jù)不改變,將數(shù)據(jù)din
拼接起始位和停止位鎖存到data
中
三丶上板驗證
因為設(shè)置的FIFO存儲滿8個數(shù)據(jù)才開始讀數(shù)據(jù),所以這里看到發(fā)送8’h88之后才收到數(shù)據(jù)?。?!
四丶源碼
https://github.com/xuranww/uart_fifo.git
參考文章:
1.https://www.cnblogs.com/xuqing125/p/8337586.html
2.https://blog.csdn.net/QWERTYzxw/article/details/121295258文章來源:http://www.zghlxwxcb.cn/news/detail-412761.html
-
緩沖區(qū)(Buffer)就是在內(nèi)存中預(yù)留指定大小的存儲空間用來對I/O的數(shù)據(jù)做臨時存儲,這部分預(yù)留的內(nèi)存空間叫緩沖區(qū)。
使用緩沖區(qū)有兩個好處:
①減少實際物理讀寫次數(shù)
②緩沖區(qū)在創(chuàng)建時就被分配內(nèi)存,這塊內(nèi)存區(qū)域一直被重用,可以減少動態(tài)分配和回收內(nèi)存的次數(shù) ??文章來源地址http://www.zghlxwxcb.cn/news/detail-412761.html
到了這里,關(guān)于【FPGA】UART串口通信---基于FIFO的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!