軟件版本:Anlogic -TD5.9.1-DR1_ES1.1
操作系統:WIN10 64bit
硬件平臺:適用安路(Anlogic)FPGA
實驗平臺:米聯客-MLK-L1-CZ06-DR1M90G開發板
板卡獲取平臺:https://milianke.tmall.com/
登錄“米聯客”FPGA社區 http://www.uisrc.com 視頻課程、答疑解惑!
目錄
1?概述
2?程序設計
2.1 系統框圖
2.2?驅動源碼
3?RTL仿真
3.1 仿真激勵文件
3.2?SPI發送驅動代碼仿真CPHA=0 CPOL=0
3.3?SPI發送驅動代碼仿真CPHA=1 CPOL=0
3.4?SPI發送驅動代碼仿真CPHA=0 CPOL=1
3.5?SPI發送驅動代碼仿真CPHA=1 CPOL=1
1?概述
SPI的發送器驅動程序主要圍繞SPI_MOSI以及SPI_SCLK來設計。通過前面的SPI協議學習,我們這里設計的SPI驅動程序需要支持CPHA=0 CPOL=0;?CPHA=1 CPOL=0;?CPHA=0 CPOL=1;?CPHA=1 CPOL=1四種情況。CPHA用于控制SPI接收器的采樣時鐘位置,CPOL用于設置SPI_SCLK的初始電平是高電平還是低電平。
2?程序設計
2.1 系統框圖
本次實驗設計一個SPI?Master(SPI_MOSI)發送驅動,包含SPI的四種工作模式。SPI?Master(SPI_MOSI)共有兩個模塊,分別為頂層模塊spi_master_tx和發送驅動模塊ui_mspi_tx。我們米聯客設計的驅動接口,一般將接口驅動程序和驅動控制程序分開編寫,這樣的好處可以讓代碼層次更加清晰,實用維護更加方便。
SPI Master發送驅動器模塊:
根據上一節課關于SPI通信原理的學習,我們知道要開始SPI通信,主機必須發送時鐘信號,系統時鐘一般運行于較高速度,而SPI的SCLK需要基于系統時鐘分頻后產生,所以首先需要設計一個分頻器,并設置CPOL信號控制SCLK的空閑狀態。并行數據需要通過MOSI總線發送出去,因此需要一個并串移位模塊,將并行數據轉成串行數據一位一位發送出去,并設置CPHA信號控制數據的采樣時刻。
為了方便SPI Master主控制器可以方便使用該驅動程序,設計數據控制器模塊,用來保存要發送的數據。使用I_spi_tx_req以及O_spi_busy用于信號的握手,在以后米聯客的代碼中,接口之間的握手也會采用類似信號和時序。用戶程序通過設置I_spi_tx_req為高,請求發送驅動器發送數據;設置O_spi_busy為1,表示發送總線正忙,這時用戶程序需要等待非忙的時候,請求發送數據。
根據以上分析,發送驅動程序包含基本的時鐘分頻器、數據控制器、并串移位模塊、CPOL控制、CPHA控制。
時鐘分頻器模塊:
系統時鐘一般運行于較高速度,而SPI的SCLK需要基于系統時鐘分頻后產生,所以首先需要設計一個分頻器,用于對SCLK分頻,當spi_en拉高代表啟動傳輸,clk_div開始計數,計滿清0。
localparam?[9:0] SPI_DIV ? ? = CLK_DIV;?????????????????????????????//第二時鐘邊沿計數器
localparam?[9:0] SPI_DIV1 ? ?= SPI_DIV/2;???????????????????????????//第一時鐘邊沿計數器always@(posedge?I_clk)begin??????????????????????????????????????????//時鐘分頻器if(spi_en == 1'b0)clk_div <= 10'd0;else?if(clk_div < SPI_DIV)clk_div <= clk_div + 1'b1;else?clk_div <= 0;
end
SCLK模塊:
SCLK可以支持CPOL=0(空閑狀態輸出低電平)和CPOL=1(空閑狀態輸出高電平)
首先我們可以設置一個內部參考時鐘,這個時鐘默認的時鐘極性為CPOL=0的情況,當我們設置CPOL=0或者CPOL=1的時候我們只要對時鐘采取不取反或者取反操作,最后賦值給O_spi_sclk。
內部的SCLK通過clk_en1和clk_en2的觸發時刻來實現電平的輸出和切換。
assign?? ? ?clk_en1 ? ? = (clk_div == SPI_DIV1);???????????????//第一內部時鐘邊沿使能
assign?? ? ?clk_en2 ? ? = (clk_div == SPI_DIV);?????????????????//第二內部時鐘邊沿使能
assign?? ? ?O_spi_sclk ?= (CPOL == 1'b1) ? ~spi_clk : spi_clk;//設置SPI時鐘的初始電平always@(posedge?I_clk)begin?????????????????????????????????????//生成spi內部時鐘if(spi_en == 1'b0)spi_clk <= 1'b0;else?if(clk_en2)spi_clk <= 1'b0;?????????????????????????????????//第二時鐘邊沿else?if(clk_en1&&(tx_cnt<4'd8))?????????????????????//第一時鐘邊沿spi_clk <= 1'b1;elsespi_clk <= spi_clk;
end
數據控制器設計:
數據控制器是SPI-Master發送驅動器設計中最關鍵的部分,數據控制器包括驅動控制接口,也包含了SPI數據部分的并串移位模塊。
當SPI的控制器部分發送了I_spi_tx_req為高電平后,下一個系統時鐘周期數據會被寄存到spi_tx_data_r并且設置spi_en為高電平,之后時鐘分頻模塊、SCLK模塊等開始工作。同時設置spi_busy信號為高電平,通知SPI控制器SPI驅動器已經處于工作狀態。
assign?? ? ?clk_end ? ? = (clk_div == SPI_DIV1)&&(tx_cnt==4'd8);
assign?? ? ?O_spi_mosi ?= spi_tx_data_r[7];
assign?? ? ?O_spi_busy ?= spi_en;
……
……//spi發送模塊
always@(posedge?I_clk)begin?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //spi發送模塊if(I_rstn == 1'b0?|| clk_end)beginspi_en <= 1'b0;spi_tx_data_r <= 8'h00;endelse?if(I_spi_tx_req&&(spi_en == 1'b0)) begin?? ? ? ? ? ? ? ? ? ?//啟用傳輸spi_en <= 1'b1;spi_tx_data_r <= I_spi_tx_data;endelse?if(spi_en)beginspi_tx_data_r[7:0] <= (spi_strobe) ? {spi_tx_data_r[6:0],1'b1} : spi_tx_data_r;end
end??
移位數據的更新通過spi_stroble控制,spi_stroble根據CPHA的設置決定是clk_en1更新數據還是clk_en2更新數據。clk_en1和SCLK的第1個跳變沿同步,clk_en2和SCLK的第2個跳變沿同步。
//當CPHA=0時,數據的第一個SCLK轉換邊緣被采樣,因此數據更新在第二個轉換邊緣上
//當CPHA=1時,數據的第二個SCLK轉換邊緣被采樣,因此數據更新在第一個轉換邊緣上assign?? ? ?spi_strobe ?= CPHA ? clk_en1&spi_strobe_en : clk_en2&spi_strobe_en ;always@(posedge?I_clk)begin??if(I_rstn == 1'b0)spi_strobe_en <= 1'b0;else?if(tx_cnt < 4'd8)beginif(clk_en1)spi_strobe_en <= 1'b1; ? ?elsespi_strobe_en <= spi_strobe_en;end?else?spi_strobe_en <= 1'b0; ? ? ? ?
endalways@(posedge?I_clk)begin??if((I_rstn == 1'b0)||(spi_en == 1'b0))tx_cnt <= 4'd0;else?if(clk_en1)tx_cnt <= tx_cnt + 1'b1; ? ? ?
end
SPI Master發送控制器設計:
發送控制器設計核心部分在于狀態機的設計。M_S狀態機只有2個狀態,M_S==0狀態等待SPI-Master驅動器非忙的情況下,發送數據發送請求信號,并且在M_S==1狀態等待數據確認進入忙狀態后,再次回到狀態0等待空閑,如果總線空閑發送下一個測試數據。
在SPI Master發送控制器的設計中,核心狀態機部分首先設置spi_tx_req=1啟動一次SPI傳輸(狀態0),發送一次數據,下一個待發送數據計數加1并存儲在spi_tx_data,開始傳輸后進入狀態1,spi_busy為高電平時代表正在傳輸,設置spi_tx_req=0,并且等待spi_busy變為低電平,之后可以進行下一次的數據傳輸。
2.2?驅動源碼
`timescale 1ns / 1ps //定義仿真時間刻度/精度module ui_mspi_tx#
(
parameter CLK_DIV = 100,
parameter CPOL = 1'b0, //時鐘極性參數設置
parameter CPHA = 1'b0 //時鐘相位參數設置
)
(
input I_clk, //系統時鐘輸入
input I_rstn, //系統復位輸入
output O_spi_mosi, //發送SPI數據
output O_spi_sclk, //發送SPI時鐘
input I_spi_tx_req, //發送數據請求
input [7:0] I_spi_tx_data, //發送數據
output O_spi_busy //發送狀態忙,代表正在發送數據
);localparam [9:0] SPI_DIV = CLK_DIV; //第二時鐘邊沿計數器
localparam [9:0] SPI_DIV1 = SPI_DIV/2; //第一時鐘邊沿計數器reg [9:0] clk_div = 10'd0;
reg spi_en = 1'b0;
reg spi_clk = 1'b0;
reg [3:0] tx_cnt = 4'd0;
reg [7:0] spi_tx_data_r=8'd0;
wire clk_end;
wire clk_en1; //第一內部時鐘邊沿使能
wire clk_en2; //第二內部時鐘邊沿使能
reg spi_strobe_en;
wire spi_strobe; //CPHA=0數據在第一時鐘邊沿上傳輸,CPHA=1數據在第二時鐘邊沿上發送assign clk_en1 = (clk_div == SPI_DIV1);//第一內部時鐘邊沿使能
assign clk_en2 = (clk_div == SPI_DIV);//第二內部時鐘邊沿使能
assign clk_end = (clk_div == SPI_DIV1)&&(tx_cnt==4'd8);
//計數器發送第一個內部時鐘0到7次,當計數達到最后8時,不發送時鐘//當CPHA=0時,數據的第一個SCLK轉換邊緣被采樣,因此數據更新在第二個轉換邊緣上
//當CPHA=1時,數據的第二個SCLK轉換邊緣被采樣,因此數據更新在第一個轉換邊緣上
assign spi_strobe = CPHA ? clk_en1&spi_strobe_en : clk_en2&spi_strobe_en ;
assign O_spi_sclk = (CPOL == 1'b1) ? ~spi_clk : spi_clk;//設置SPI時鐘的初始電平
assign O_spi_mosi = spi_tx_data_r[7];
assign O_spi_busy = spi_en;always@(posedge I_clk)begin //時鐘分頻器if(spi_en == 1'b0)clk_div <= 10'd0;else if(clk_div < SPI_DIV)clk_div <= clk_div + 1'b1;else clk_div <= 0;
end
always@(posedge I_clk)begin //生成spi內部時鐘if(spi_en == 1'b0)spi_clk <= 1'b0;else if(clk_en2) spi_clk <= 1'b0; //第二時鐘邊沿else if(clk_en1&&(tx_cnt<4'd8)) //第一時鐘邊沿spi_clk <= 1'b1; elsespi_clk <= spi_clk;
endalways@(posedge I_clk)begin if(I_rstn == 1'b0) spi_strobe_en <= 1'b0;else if(tx_cnt < 4'd8)beginif(clk_en1) spi_strobe_en <= 1'b1; end else spi_strobe_en <= 1'b0;
endalways@(posedge I_clk)begin if((I_rstn == 1'b0)||(spi_en == 1'b0)) tx_cnt <= 4'd0;else if(clk_en1) tx_cnt <= tx_cnt + 1'b1;
endalways@(posedge I_clk)begin //spi發送模塊if(I_rstn == 1'b0 || clk_end)beginspi_en <= 1'b0;spi_tx_data_r <= 8'h00;endelse if(I_spi_tx_req&&(spi_en == 1'b0)) begin //啟用傳輸spi_en <= 1'b1;spi_tx_data_r <= I_spi_tx_data;endelse if(spi_en)beginspi_tx_data_r[7:0] <= (spi_strobe) ? {spi_tx_data_r[6:0],1'b1} : spi_tx_data_r;endend endmodule
SPI Master發送控制器源碼
SPI Master的發送控制器根據不同的實際應用需要一次或者多出把一個或者多個數據發送出去,在本實驗中,演示了發送連續的加計數器數據的方法。
`timescale 1ns / 1psmodule spi_master_tx#
(
parameter CLK_DIV = 100
)
(
input I_clk, //輸入時鐘
input I_rstn, //系統復位
output O_spi_sclk, //SPI發送時鐘
output O_spi_mosi //SPI發送數據
);wire spi_busy; //SPI忙信號
reg spi_tx_req; //SPI發送req信號,有發送需求時拉高
reg [7:0] spi_tx_data; //待發送數據存儲
reg [1:0] M_S; //狀態機//spi send state machine
always @(posedge I_clk) beginif(!I_rstn) begin //拉低復位spi_tx_req <= 1'b0;spi_tx_data <= 8'd0;M_S <= 2'd0;endelse begincase(M_S)0:if(!spi_busy)begin //總線不忙啟動傳輸spi_tx_req <= 1'b1; //req信號拉高,開始傳輸spi_tx_data <= spi_tx_data + 1'b1; //測試數據M_S <= 2'd1;end1:if(spi_busy)begin //如果spi總線忙,清除spi_tx_reqspi_tx_req <= 1'b0;M_S <= 2'd0;enddefault:M_S <= 2'd0;endcaseend
end //例化SPI Master發送驅動器
ui_mspi_tx#
(
.CLK_DIV(CLK_DIV),
.CPOL(1'b0), //CPOL參數設置,可調整
.CPHA(1'b0) //CPHA參數設置,可調整
)
ui_mspi_tx_inst(
.I_clk(I_clk), //系統時鐘輸入
.I_rstn(I_rstn), //系統復位輸入
.O_spi_mosi(O_spi_mosi), //SPI發送數據串行總線
.O_spi_sclk(O_spi_sclk), //SPI發送時鐘總線
.I_spi_tx_req(spi_tx_req), //SPI發送(寫)數據請求
.I_spi_tx_data(spi_tx_data), //SPI發送(寫)數據
.O_spi_busy(spi_busy) //SPI發送驅動器忙);
endmodule
M_S狀態機只有2個狀態,M_S==0狀態等待SPI-Master驅動器非忙的情況下,發送數據發送請求信號,并且在M_S==1狀態等待數據確認進入忙狀態后,再次回到狀態0等待空閑,如果總線空閑發送下一個測試數據。
3?RTL仿真
3.1 仿真激勵文件
Modelsim仿真的創建過程不再重復,如有不清楚的請看前面實驗
本實驗以仿真的方式演示,仿真激勵信號提供一個系統時鐘即可
`timescale 1ns / 1ps
module sim_top_tb();
localparam SYS_TIME = 'd20;//時鐘周期,以ns為單位
reg I_sysclk; //系統時鐘
reg rstn_i;
wire spi_sclk_o;
wire spi_mosi_o;spi_master_tx#
(
.CLK_DIV(100) //設置時鐘參數,可以減少仿真時間
)
spi_master_tx_inst(
.I_clk(I_sysclk),
.I_rstn(rstn_i),
.O_spi_sclk(spi_sclk_o),
.O_spi_mosi(spi_mosi_o)
);initial beginI_sysclk = 1'b0; //設置時鐘基礎值rstn_i = 1'b0; //低電平復位#100;rstn_i = 1'b1; //復位釋放#2000000 $finish;
endalways #(SYS_TIME/2) I_sysclk = ~I_sysclk; //產生主時鐘endmodule
以下啟動modelsim仿真
3.2?SPI發送驅動代碼仿真CPHA=0 CPOL=0
如下圖所示,當CPHA=0 CPOL=0,代表SPI的SCLK默認是低電平,SPI接收器在SCLK第1個時鐘沿采樣。SPI發送驅動器數據在SCLK的第2個時鐘沿更新,確保SPI下一個SCLK的第1個時鐘沿數據有足夠的建立和保持時間。下圖以發送8’h02為例。
3.3?SPI發送驅動代碼仿真CPHA=1 CPOL=0
如下圖所示,當CPHA=1 CPOL=0,代表SPI的SCLK默認是低電平,SPI接收器在SCLK第2個時鐘沿采樣。SPI發送驅動器數據在下一個SCLK的第1個時鐘沿更新,確保SPI下一個SCLK的第2個時鐘沿數據有足夠的建立和保持時間。下圖以發送8’h02為例。
3.4?SPI發送驅動代碼仿真CPHA=0 CPOL=1
和CPHA=0 CPOL=0這種設置相比,時鐘SCLK取反
3.5?SPI發送驅動代碼仿真CPHA=1 CPOL=1
和CPHA=1 CPOL=0這種設置相比,時鐘SCLK取反