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,
這里我選擇輸出讀空、讀滿、寫空、寫滿等信號以備后面實驗,到這里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 endmodulefifo寫模塊
//讀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 endmodulefifo讀模塊
讀寫模塊需要注意,我是以寫滿和寫空為寫操作的判斷基準的,讀操作的判斷也是一樣的,當然正常使用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://pan.baidu.com/s/1S9R0JVtANzVGb4slCgGBMw
提取碼:yfe4
感興趣的可以試試啦
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/173980.html
標籤:Verilog
上一篇:十分鐘教會你使用Python操作excel,內附步驟和代碼!python其實很簡單
下一篇:001:FPGA入門——呼吸燈
