FPGA -- SPI 時序實現(超級靈活,超級好用)
前言
現在大多數的器件都是採用SPI進行通訊,但是有的是標準的SPI,由的卻不是標準的SPI通訊(不遵循CPOL CPHA),為了相容標準與不標準SPI,特意編寫了靈活性較強的SPI。
一、SPI
在Stm32 手冊中規定了 SPI的四種形式,我稱之為標準的SPI,可以從圖中發現在資料發生變化的時間有所不同,這主要是由CPHA決定的若CPHA=1則意味著資料資料是在時鐘變化之後變化的,若CPHA=0則意味著資料資料是在時鐘變化之前變化的;而CPOL代表的是CLK在閒時的電平。
標準和不標準之說,則是在資料接收時的異同,不標準的SPI在資料接收時,會指定在時鐘的上升沿/下降沿返回資料,此類的SPI大多數是某些公司定製的。標準的SPI則依舊按照上圖返回資料。
二、程式碼說明
埠介紹:
module spi_master_v3 #(
parameter TX_DATA_WIDTH = 16,
parameter RX_DATA_WIDTH = 16
)(
input i_sys_clk ,
input i_reset_n ,
input i_start , //傳送觸發訊號,上升沿有效
input i_load_again,
input i_CPOL , //時鐘極性 閒時為CPOL
input i_CPHA , //時鐘相位 1:在資料來之前變化 0:在是資料來之後發生變化
input i_CS_nVAl , //片選訊號有效電平
input i_MSB , //1:大端模式 0:小端模式
input i_RX_nDown , //1:上升沿收資料 0:下降沿收資料
input i_RX_ndirc_L, //1:先接收高位 0:先接收低位
input [7:0]i_div_num ,
input [7:0]i_tx_bit_num ,
input [7:0]i_rx_bit_num ,
input [3:0]i_delay_cycle , //CS延遲的節拍數
input [TX_DATA_WIDTH-1:0]i_tx_data,
output [TX_DATA_WIDTH-1:0]o_rx_data,
output reg o_axi_vali ,
input i_axi_rdy ,
output o_clk ,
output o_cs ,
output o_mosi ,
input i_miso ,
output o_busy
);
load_again 的原理及使用方法:
在i_start 的上升沿時,採集第一次 i_load_again 的值,若i_load_again訊號為高,那麼
表示主機方,想傳送突發資料(可以指定每次突發資料的長度),主機方想傳送突發資料時,
只需要將 i_axi_rdy 訊號拉高,通知模組準備接收突發資料流,並指定是否還有突發資料,若想
結束突發資料傳送,只需將i_load_again拉低即可,模組在接收完成突發資料時,會將o_axi_vali
訊號拉高;若在第一次傳送資料時,把 i_load_again 拉低則表明只接收幀資料,此時拉高
i_axi_rdy 無效。
1.程式原始碼
`timescale 1ns / 100ps
//
// Company:
// Engineer:
//
// Create Date: 22:41:44 2020年10月26日
// Design Name:
// Module Name: spi_master_v3
// Project Name:
// Target Devices:
// Tool versions:
// Description:
/*
此版本的SPi總的來說能都應對絕大部分 SPI時序,其中在版本中加入了load_again功能,以及
i_RX_nDown,i_RX_ndirc_L,具體的使用見埠註釋
load_again 的原理及使用方法:
在i_start 的上升沿時,採集第一次 i_load_again 的值,若i_load_again訊號為高,那麼
表示主機方,想傳送突發資料(可以指定每次突發資料的長度),主機方想傳送突發資料時,
只需要將 i_axi_rdy 訊號拉高,通知模組準備接收突發資料流,並指定是否還有突發資料,若想
結束突發資料傳送,只需將i_load_again拉低即可,模組在接收完成突發資料時,會將o_axi_vali
訊號拉高;若在第一次傳送資料時,把 i_load_again 拉低則表明只接收幀資料,此時拉高
i_axi_rdy 無效。
*/
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module spi_master_v3 #(
parameter TX_DATA_WIDTH = 16,
parameter RX_DATA_WIDTH = 16
)(
input i_sys_clk ,
input i_reset_n ,
input i_start ,
input i_load_again,
input i_CPOL , //時鐘極性 閒時為CPOL
input i_CPHA , //時鐘相位 1:在資料來之前變化 0:在是資料來之後發生變化
input i_CS_nVAl , //片選訊號低電平有效
input i_MSB , //1:大端模式 0:小端模式
input i_RX_nDown , //1:上升沿收資料 0:下降沿收資料
input i_RX_ndirc_L, //1:先接收高位 0:先接收低位
input [7:0]i_div_num ,
input [7:0]i_tx_bit_num ,
input [7:0]i_rx_bit_num ,
input [3:0]i_delay_cycle , //CS延遲的節拍數
input [TX_DATA_WIDTH-1:0]i_tx_data,
output [TX_DATA_WIDTH-1:0]o_rx_data,
output reg o_axi_vali ,
input i_axi_rdy ,
output o_clk ,
output o_cs ,
output o_mosi ,
input i_miso ,
output o_busy
);
/*********************************************************/
reg [1:0]start_buff;
always @(posedge i_sys_clk or negedge i_reset_n) begin
if(!i_reset_n)
start_buff <= 0;
else
start_buff <= {start_buff[0] , i_start};
end
wire start_flag = (start_buff == 2'b01);
/*********************************************************/
reg [7:0]div_num ;
reg [7:0]tx_bit_cnt_f;
reg [7:0]rx_bit_cnt_f;
reg [7:0]tx_bit_cnt_load;
reg [3:0]delay_cycle;
reg [TX_DATA_WIDTH-1:0]tx_load_again_data;
reg load_again;
reg flash_data;
reg load_again_data_done;
always @(posedge i_sys_clk or negedge i_reset_n) begin
if(!i_reset_n) begin
div_num <= 0;
delay_cycle <= 0;
load_again <= 0;
flash_data <= 0;
tx_bit_cnt_load <= 0;
tx_load_again_data <= 0;
end
else if(start_flag)begin
div_num <= i_div_num;
tx_bit_cnt_f <= i_tx_bit_num;
rx_bit_cnt_f <= i_rx_bit_num;
delay_cycle <= i_delay_cycle;
load_again <= i_load_again;
o_axi_vali <= 0;
flash_data <= 0;
end
else if(load_again_data_done) begin
flash_data <= 0;
o_axi_vali <= 0;
end
else if(load_again && i_axi_rdy ) begin //採集下一次的load_again訊號
tx_bit_cnt_load <= i_tx_bit_num;
load_again <= i_load_again;
tx_load_again_data <= i_tx_data; //接收資料已經飽和
o_axi_vali <= 1;
flash_data <= 1;
end
else begin
o_axi_vali <= 0;
end
end
/*********************************************************/
localparam idle = 1;
localparam s1 = idle << 1;
localparam s2 = idle << 2;
localparam s3 = idle << 3;
localparam s4 = idle << 4;
(* fsm_coding = "One-HOt" *)reg [4:0]curr_sta;
(* fsm_coding = "One-HOt" *)reg [4:0]next_sta;
//fsm_1
always @(posedge i_sys_clk or negedge i_reset_n) begin
if(!i_reset_n)
curr_sta <= idle;
else
curr_sta <= next_sta;
end
//fsm_2
reg tx_start;
reg delay_clr;
wire delay_cs_ok;
reg [7:0]tx_bit_cnt;
always @(*) begin
next_sta = idle;
if(!i_reset_n) begin
next_sta = idle;
tx_start = 0;
delay_clr = 1;
end
else begin
case (curr_sta)
idle :begin
if(start_flag)
next_sta = s1;
else
next_sta = idle;
tx_start = 0;
delay_clr = 1;
end
s1:begin
if(delay_cs_ok) begin
next_sta = s2;
delay_clr = 1;
end
else begin
next_sta = s1;
delay_clr = 0;
end
tx_start = 0;
end
s2:begin
if(tx_bit_cnt == tx_bit_cnt_f) //傳輸完成一次
next_sta = flash_data ? s4 : s3;
else
next_sta = s2;
tx_start = 1;
delay_clr = 1;
end
s3:begin
if(delay_cs_ok) begin
next_sta = idle;
delay_clr = 1;
end
else begin
next_sta = s3;
delay_clr = 0;
end
tx_start = 0;
end
s4:begin
if(load_again_data_done)
next_sta = s2;
else
next_sta = s4;
end
default:next_sta = idle;
endcase
end
end
//fsm_3
reg cs;
reg clk;
reg [TX_DATA_WIDTH-1:0]tx_data;
wire clk_half;
wire clk_full;
always @(posedge i_sys_clk or negedge i_reset_n) begin
if(!i_reset_n)begin
cs <= ~i_CS_nVAl;
clk <= i_CPOL;
tx_bit_cnt <= 0;
load_again_data_done <= 0;
end
else begin
case(curr_sta)
idle:begin
cs <= ~i_CS_nVAl;
clk <= i_CPOL;
load_again_data_done <= 0;
end
s1:begin
cs <= i_CS_nVAl;
clk <= i_CPOL;
tx_bit_cnt <= 0;
tx_data <= i_tx_data;
if(delay_cs_ok && i_CPHA)
clk <= ~i_CPOL;
else
clk <= i_CPOL;
end
s2:begin
if(tx_bit_cnt == tx_bit_cnt_f - 1 && clk_full && i_CPHA && !flash_data )
clk <= i_CPOL;
else if(clk_half || clk_full)
clk <= ~clk;
if(clk_full) begin
tx_bit_cnt <= tx_bit_cnt + 1'b1;
tx_data <= i_MSB ? tx_data << 1 : tx_data >> 1;
end
load_again_data_done <= 0;
end
s3:begin
//cs <= ~i_CS_nVAl;
end
s4:begin //裝載loag_again_data
tx_data <= tx_load_again_data;
tx_bit_cnt_f <= tx_bit_cnt_load;
tx_bit_cnt <= 0;
load_again_data_done <= 1;
end
default:;
endcase
end
end
assign o_clk = clk;
assign o_mosi= i_MSB ? tx_data[TX_DATA_WIDTH - 1]:tx_data[0];
assign o_cs = cs;
assign o_busy = (curr_sta !=idle);
/******************************************************************************/
//rx data
wire [7:0]delay_bit = tx_bit_cnt_f - rx_bit_cnt_f;
reg [7:0]rx_bit_cnt;
reg [RX_DATA_WIDTH-1:0]rx_data;
reg clk_f;
always @(posedge i_sys_clk )
clk_f <= clk;
wire clk_up = (!clk_f) & clk;
wire clk_dn = (!clk) & clk_f;
always @(posedge i_sys_clk or negedge i_reset_n) begin
if(!i_reset_n) begin
rx_bit_cnt <= 0;
rx_data <= 0;
end
else if(start_flag) begin
rx_bit_cnt <= 0;
rx_data <= 0;
end
else if(((!i_RX_nDown && clk_dn) || (i_RX_nDown && clk_up))&& rx_bit_cnt >= delay_bit) begin
if(i_RX_ndirc_L) //先收低位資料
rx_data <= {rx_data[RX_DATA_WIDTH-2:0],i_miso};
else
rx_data <= {i_miso,rx_data[RX_DATA_WIDTH-1:1]};
rx_bit_cnt <= rx_bit_cnt + 1;
end
else if((!i_RX_nDown && clk_dn) || (i_RX_nDown && clk_up))begin
rx_bit_cnt <= rx_bit_cnt + 1;
end
end
assign o_rx_data = i_RX_ndirc_L ? rx_data : rx_data >>(RX_DATA_WIDTH - delay_bit) ;
/******************************************************************************/
reg [7:0]div_num_cnt;
always @(posedge i_sys_clk or negedge i_reset_n) begin
if(!i_reset_n)
div_num_cnt <= 0;
else if(tx_start)begin
if(div_num_cnt == div_num)
div_num_cnt <= 0;
else
div_num_cnt <= div_num_cnt + 1;
end
else
div_num_cnt <= 0;
end
assign clk_half = (div_num_cnt == div_num >> 1);
assign clk_full = (div_num_cnt == div_num >> 0);
/******************************************************************************/
reg [3:0]delay_cnt;
always @(posedge i_sys_clk or negedge i_reset_n) begin
if(!i_reset_n)
delay_cnt <= 0;
else if(delay_clr)
delay_cnt <= 0;
else
delay_cnt <= delay_cnt + 1;
end
assign delay_cs_ok = (delay_cnt == delay_cycle);
/******************************************************************************/
function integer calc_width(input integer num);
begin
calc_width = 0;
while(num > 0) begin
num = num >> 1 ;
calc_width = calc_width + 1;
end
end
endfunction
/******************************************************************************/
endmodule
2.模擬原始碼
`timescale 1ns / 100ps
//
// Company:
// Engineer: Deam
//
// Create Date: 21:39:35 2020年10月30日
// Design Name:
// Module Name: tb_spi_master_v3
// Project Name:
// Target Devices:
// Tool versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module tb_spi_master_v3 ;
reg i_sys_clk;
reg i_reset_n;
always #0.5 i_sys_clk =~i_sys_clk;
initial begin
i_sys_clk = 0;
i_reset_n = 0;
#2
i_reset_n = 1;
end
/******************************************************************************/
parameter TX_DATA_WIDTH = 16;
parameter RX_DATA_WIDTH = 16;
reg i_start;
reg i_miso;
reg i_load_again;
reg i_CS_nVAl;
reg i_CPOL;
reg i_CPHA;
reg i_MSB;
reg i_RX_ndirc_L;
reg i_axi_rdy;
reg i_RX_nDown;
reg [7:0]i_div_num;
reg [7:0]i_tx_bit_num;
reg [7:0]i_rx_bit_num;
reg [3:0]i_delay_cycle;
reg [TX_DATA_WIDTH-1:0]i_tx_data;
wire [RX_DATA_WIDTH-1:0]o_rx_data;
wire o_clk;
wire o_cs;
wire o_mosi;
wire o_busy;
spi_master_v3 #(
.TX_DATA_WIDTH (TX_DATA_WIDTH ),
.RX_DATA_WIDTH (RX_DATA_WIDTH )
)uut(
.i_sys_clk (i_sys_clk ),
.i_reset_n (i_reset_n ),
.i_start (i_start ),
.i_tx_data (i_tx_data ),
.i_tx_bit_num (i_tx_bit_num),
.i_rx_bit_num (i_rx_bit_num),
.i_div_num (i_div_num ),
.i_CPOL (i_CPOL ),
.i_CS_nVAl (i_CS_nVAl ),
.i_CPHA (i_CPHA ),
.i_MSB (i_MSB ),
.i_RX_nDown (i_RX_nDown ),
.i_RX_ndirc_L (i_RX_ndirc_L),
.i_load_again (i_load_again),
.o_rx_data (o_rx_data ),
.i_delay_cycle (i_delay_cycle),
.i_axi_rdy (i_axi_rdy ),
.o_axi_vali (o_axi_vali ),
.o_clk (o_clk ),
.o_cs (o_cs ),
.o_mosi (o_mosi ),
.i_miso (i_miso ),
.o_busy (o_busy )
);
/******************************************************************************/
always @(negedge o_clk)
i_miso <= $random;
initial begin
i_start = 0;
i_miso = 0;
i_load_again = 0;
i_CS_nVAl = 0;
i_CPOL = 0;
i_CPHA = 0;
i_MSB = 1;
i_RX_nDown = 0;
i_RX_ndirc_L = 0;
i_delay_cycle = 1;
i_axi_rdy = 0;
i_div_num = 20;
i_tx_bit_num = 8;
i_rx_bit_num = 4;
#10;
/* repeat (3) begin
wait(!o_busy);
#10;
send_data();
end */
send_data();
#50 i_axi_rdy = 1;
//send_data();
#100 i_load_again = 0;
end
/******************************************************************************/
task send_data;
begin
#1;
i_start = 1;
i_tx_data = $random;
i_div_num = 10;
#1;
i_start = 0;
end
endtask
/******************************************************************************/
endmodule
3. do 檔案原始碼
# ./當前路徑
# ../上一級目錄
#退出當前模擬
quit -sim
#清空命令列
.main clear
#建立庫
#vlib lib
#vib ./lib/work
#將建立庫對映至物理邏輯庫
#vmap work .lib/work
#編譯檔案
vlog -work work ./../spi_master_v3.v
vlog -work work ./../tb_spi_master_v3.v
#啟動模擬
vsim -voptargs=+acc work.tb_spi_master_v3
#新增波形
do tb_master_v3_wave.do
#執行模擬
run 500ns
################################################################################3
#10進位制顯示波形
#add wave -radix unsigned /tb_usart_rx/o_data
#模擬顯示
#add wave -format Analog-Step -height 74 -max 255.0 /tb_usart_rx/o_data
三、模擬結果
在這裡列出了使用loag_again 和不適用load_again的兩種情況,更多情況請自行模擬,若出現錯誤,歡迎指出。內容為原創,原創不易。
相關文章
- JAVA 實現《超級瑪麗升級版》遊戲Java遊戲
- 小程式生態是更靈活構建超級App的方式APP
- 一些超級好用的CSS 屬性CSS
- iOS超級超級詳細介紹GCDiOSGC
- 【推薦】Python超級好用的裝飾器!Python
- 為實現電動車長途旅行,特斯拉超級充電站將大幅升級
- 超級小精靈 ver1.0破解 (448字)
- 基於 Electron 實現 uTools 的超級皮膚
- 超級英雄遊戲需要拋棄「現實主義」遊戲
- 輕量級超級 css 工具CSS
- HttpSender OkHttp+RxJava超好用、功能超級強大的Http請求框架HTTPRxJava框架
- 超級鋼琴
- 超級膠水
- 超級表達
- ES6中陣列新增的方法-超級好用陣列
- 超級好用的mac防火牆軟體:Little Snitch for MacMac防火牆
- 5款超級好用的桌面端軟體推薦
- 能復活超級英雄的除了時間寶石,還有量子計算機?計算機
- 超級好用的線上翻譯工具:Translatium Mac中文版Mac
- 超級好用的磁碟清理軟體:DaisyDisk for mac中文版AIMac
- WebService之超級HelloWorldWeb
- 超級SQL--------OracleSQLOracle
- 蘋果超級簽名的實現過程、技術原理蘋果
- Android如何實現超級棒的沉浸式體驗Android
- 超級簡單的實現window共享Linux檔案Linux
- 天河二號:中國的超級計算機及其超級應用計算機
- 十個超級實用的 JS 特性JS
- 一款超級實用的SuperLayout
- 超級實用!React-Router v6實現頁面級按鈕許可權React
- 前端技術分享:一個超級好用的CSS樣式表前端CSS
- 超級好用的檔案管理器:File Cabinet Pro for MacMac
- 如何使用抽象,虛方法,重寫,看了這個示例,超級好用抽象
- 安利一款超級好用的 Dubbo 除錯工具 Apifox除錯API
- 眾盟超級推的“私域營銷”,如何實現人人優活的流量十倍增?
- 超級程式設計師程式設計師
- 超級有用的網站網站
- HIVE的許可權控制和超級管理員的實現Hive
- Python實現超級瑪麗遊戲系列教程01瑪麗登場Python遊戲