FPGA -- SPI 時序實現(超級靈活,超級好用)

a1324889197發表於2020-11-15

前言

現在大多數的器件都是採用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的兩種情況,更多情況請自行模擬,若出現錯誤,歡迎指出。內容為原創,原創不易。

相關文章