FPGA に基づいた QSPI の基礎となるドライバー コードの実装

QSPI の概要

SPI プロトコルは、優秀なエンジニアの皆さんはすでによくご存じかと思いますが、SPI の正式名称は Serial Peripheral Interface で、機器間の通信伝送に広く使われている高速全二重同期通信バスです。
この記事で説明する QSPI は、SPI インターフェイスの拡張です。Q は、4 倍の伝送を意味するクワッドの略です。4 線式 SPI とも呼ばれます。したがって、このインターフェイスの伝送速度は次のようになります。標準の SPI よりもはるかに高速で、SPI フラッシュ ストレージ メディアで広く使用されています。次の記事では、フラッシュ チップのデータシートを使用して、FPGA を使用して QSPI 通信を実装する方法を詳しく説明します。

書き込みタイミング

QSPI書き込みタイミング図
タイミング図からわかるように、図には上から CE (チップセレクト信号)、CLK (クロック信号)、SIO0 ~ SIO34 データ線の合計 6 本の信号があります。SPI インターフェースと同様に、チップセレクト信号とクロック信号は変化せず、データの読み書き時はチップセレクト信号はすべて Low レベル、データのサンプリングまたは送信時はクロックの立ち上がりエッジまたは立ち下がりエッジになります。唯一の違いは、データ ラインがオリジナルの MOSI および MISO から 4 つのデータ ラインに変更されたことです。では、この 4 つのデータ ラインをどのように適用するのでしょうか? 図からわかるように、SIO0 はコマンド、アドレス、データを送信しますが、SIO1 ~ SIO3 はコマンドとデータのみを送信します。さらに観察すると、書き込みコマンドは 0x38 であり、これは 8 CLK で送信され、アドレス信号の合計がわかります。 24 ビット 6 CLK 以内に 4 本のデータ ラインで同時に送信が完了し、各データ ラインで送信される開始ビットが異なります 最後にデータが送信されます 同様に 4 本のデータ ラインが同時に送信されます1 CLK 4 ビットのデータを送信します。送信されるサイズはユーザーが設定できます。要約すると、QSPI 通信書き込みプロセスは、最初に 1 バイトのコマンド ワードを送信し (このコマンド ワードはチップごとに異なります)、次に 3 バイトのアドレスを送信し (同じ原理)、最後にデータを送信することとして要約できます。そこでFPGAの設計には工夫があり、最も簡単な方法はステートマシンを用いてこの処理を記述する方法であり、その具体的なコードを以下に示す。

読み出しタイミング

QSPI読み取りタイミング図
図からわかるように、リードタイミングの動作フローはライトタイミングと同様ですが、コマンドワードが0x38から0xEBに変更されており、その他の動作フローはライトタイミングと同じです。したがって、詳細は説明しません。
ただし、SPI が QSPI に拡張されると、全二重通信ではなく半二重通信になることに注意してください。SIO0~SIO3の4本はFPGAの入出力インターフェースであるスリーステートゲートとなり、データの入出力には特定の条件を満たす必要があります。
以下に、QSPI 通信の基礎となるドライバー コードを示します。実際のエンジニアリング アプリケーションでは、チップのデータ マニュアルと組み合わせてアプリケーション層プログラムを作成し、それを基礎となるロジックと組み合わせて、次のような特定の機能を実装する必要があります。 QSPI またはフラッシュ チップへの SPI インターフェイスの読み取りと書き込みが行われます。

QSPI実装用のVerilogコード

module QSPI_DRIVE #(
		parameter DIV = 3
)(
	input wire clk,
	input wire rst,
	//--------应用层传输进该模块的命令、地址、数据等--------//
	input wire [3:0] i_cmd_mode,
    input wire [7:0] i_flash_cmd,
    input wire [23:0] i_addr,
    input wire [7:0] i_data,
    input wire [15:0] i_data_num,
    input wire i_wr,
 	output reg [7:0] o_data,
 	//---------QSPI 接口---------//
	output reg qspi_cs,
	output reg qspi_csk,
	inout   reg qspi_sio0,
	inout   reg qspi_sio1,
	inout   reg qspi_sio2,
	inout   reg qspi_sio3
);

reg [7:0] div_cnt;
reg [7:0] cmd_cnt;
reg [7:0] addr_cnt;
reg [7:0] data_cnt;
reg [15:0] num_cnt;
reg [3:0] cmd_mode_lock;
reg [7:0] flash_cmd_lock;
reg [23:0] addr_lock;
reg [7:0] r_data_temp;
reg  qspi_sckd0;
wire qspi_sck_p,qspi_sck_n;

//---------------FSM---------------//
reg [7:0] state,n_state;
localparam  IDLE  = 8'h00,
			START = 8'h01,
			CMD   = 8'h02,
			ADDR  = 8'h04,
			DATA  = 8'h08,
			STOP  = 8'h10;
always@(posedge clk)begin
	if(rst)
		state <= IDLE;
	else
		state <= n_state;
end

always@(*)begin
	if(rst)begin
		n_state = IDLE;
	end else begin
		case(state)
			IDLE : begin
				if(i_cmd_mode[3])
					 n_state = START;
				else
					 n_state = IDLE;
		   	end
		   	START : begin
				 n_state = CMD;
			end
			CMD : begin
			 if(cmd_cnt == 8'd15)
				 if(cmd_mode_lock[1])begin
					  n_state = ADDR;
			 	 end else if(cmd_mode_lock[0])begin
					  n_state = DATA;		
				 end else begin
					  n_state = STOP;
				 end
			 else
			 	n_state = CMD;
			end
			ADDR : begin
				if(addr_cnt == 8'd12)
					if(cmd_mode_lock[0])begin
						n_state = DATA;
					end else begin
						n_state =STOP;
					end
				else
					n_state = ADDR;
			end
			DATA : begin
			 if(data_cnt == 8'd4)
				 if(cmd_mode_lock[2] && (num_cnt == 16'b0))begin
					  n_state = STOP;
			 	 end else if(!cmd_mode_lock[2])begin
					  n_state = STOP;		
				 end else begin
					  n_state = DATA;
				 end
			 else
			 	n_state = DATA;
			end	
			STOP : begin
				n_state = IDLE;
			end
			default : begin
				n_state = IDLE;
			end
		endcase
	end
end

//----------锁数据-----------//
always@(posedge clk)begin
	if(rst)begin
		cmd_mode_lock <= 4'b0;
		flash_cmd_lock <= 8'b0;
		addr_lock <= 24'b0;
	end else if(i_cmd_mode[3] && (state == IDLE))begin
		cmd_mode_lock <= i_cmd_mode;
		flash_cmd_lock <= i_flash_cmd;
		addr_lock <= i_addr;		
	end else begin
		cmd_mode_lock <= cmd_mode_lock ;
		flash_cmd_lock <= flash_cmd_lock ;
		addr_lock <= addr_lock ;
	end
end

//-----------各个功能计数器计数---------//
always@(posedge clk)begin//时钟分频,DIV为分频系数
	if(rst)
		div_cnt <= 8'h00;
	else if(div_cnt == DIV)
		div_cnt <= 8'h00;
	else if((state == CMD) || (state == ADDR) || (state == DATA ))
		div_cnt <= div_cnt + 1'b1;
	else
		div_cnt <= 8'h00;
end

always@(posedge clk)begin//命令字计数
	if(rst)
		cmd_cnt <= 8'h00;
	else if((state == CMD) && (div_cnt == DIV))
		cmd_cnt <= cmd_cnt + 1'b1;
	else if(state == CMD)
		cmd_cnt <= cmd_cnt;
	else
		cmd_cnt <= 8'h00;
end

always@(posedge clk)begin//地址计数
	if(rst)
		addr_cnt <= 8'h00;
	else if((state == ADDR) && (div_cnt == DIV))
		addr_cnt  <= addr_cnt + 1'b1;
	else if(state == ADDR)
		addr_cnt <= addr_cnt ;
	else
		addr_cnt <= 8'h00;
end

always@(posedge clk)begin//数据计数,在sck上升沿和下降沿均会加1
	if(rst)
		data_cnt <= 8'h00;
	else if((state == DATA) && cmd_mode_lock[1] &&  (data_cnt == 8'd4))
		data_cnt <= 8'h00;
	else if((state == DATA) && (qspi_sck_p || qspi_sck_n))
		data_cnt <= data_cnt + 1'b1;
	else if(state == DATA)
		data_cnt <=data_cnt;
	else
		data_cnt <= 8'h00; 
end

always@(posedge clk)begin//传输的数据长度计数,传输完成后num为0
	if(rst)
		num_cnt <= 16'h00;
	else if((state == IDLE) && i_cmd_mode[3])
		num_cnt <= i_data_num;
	else if((cmd_mode_lock[3] && (div_cnt == DIV) &&  (data_cnt == 8'd3))
		num_cnt <= num_cnt - 1'b1;
	else 
		num_cnt <=num_cnt ;
end

//-------------QSPI数据采样及发送--------------//
always@(posedge clk)begin//产生片选信号
	if(rst)
		qspi_cs <=1'b1;
	else if(state == START)
		qspi_cs <=1'b0;
	else if(state == STOP)
		qspi_cs <=1'b1;
	else
		qspi_cs <=qspi_cs ;		
end

always@(posedge clk)begin//产生qspi采样时钟
	if(rst)
		qspi_sck <=1'b0;
	else if((state == CMD) || (state == ADDR) || (state == DATA) && (div_cnt == DIV))
		qspi_sck <=!qspi_sck ;
	else if((state == CMD) || (state == ADDR) || (state == DATA))
		qspi_sck <=qspi_sck ;
	else
		qspi_sck <=1'b0;		
end

always@(posedge clk)begin
	if(rst)
		qspi_sckd0 <= 1'b1;
	else
		qspi_sckd0  <= qspi_sck;
end
assign qspi_sck_n = (qspi_sckd0 && (!qspi_sck)) ? 1'b1 : 1'b0;//取sck下降沿
assign qspi_sck_p = ((!qspi_sckd0) && qspi_sck) ? 1'b1 : 1'b0;//取sck上升沿

always@(posedge clk)begin//sio0数据线传输命令字、地址以及数据
	if(rst)
		qspi_sio0_temp <=1'b0;
	else if((state == START) || (state == ADDR) || (state == DATA) && (div_cnt == DIV))
		qspi_sio0_temp <=i_flash_cmd[7];
	else if(qspi_sck_n)begin
		if(state == CMD)
			qspi_sio0_temp <=flash_cmd_lock[7 - (cmd_cnt>>1)];
		else if(state == ADDR)
			qspi_sio0_temp <= addr_lock[20 - (addr_cnt<<1)];
		else if(state == DATA)
			qspi_sio0_temp <= i_data[4 - (data_cnt<<1)]; 
		else
			qspi_sio0_temp <= qspi_sio0_temp ;
	end else
		qspi_sio0_temp <= qspi_sio0_temp ;
end

always@(posedge clk)begin//sio1数据线传输地址以及数据
	if(rst)
		qspi_sio1_temp <=1'b0;
	else if(qspi_sck_n)begin
	    if(state == ADDR)
			qspi_sio1_temp <= addr_lock[21 - (addr_cnt<<1)];
		else if(state == DATA)
			qspi_sio1_temp <= i_data[5 - (data_cnt<<1)]; 
		else
			qspi_sio1_temp <= qspi_sio1_temp ;
	end else
		qspi_sio1_temp <= qspi_sio1_temp ;
end

always@(posedge clk)begin//sio2数据线传输地址以及数据
	if(rst)
		qspi_sio2_temp <=1'b0;
	else if(qspi_sck_n)begin
	    if(state == ADDR)
			qspi_sio2_temp <= addr_lock[22 - (addr_cnt<<1)];
		else if(state == DATA)
			qspi_sio2_temp <= i_data[6 - (data_cnt<<1)]; 
		else
			qspi_sio2_temp <= qspi_sio2_temp ;
	end else
		qspi_sio2_temp <= qspi_sio2_temp ;
end

always@(posedge clk)begin//sio3数据线传输地址以及数据
	if(rst)
		qspi_sio3_temp <=1'b0;
	else if(qspi_sck_n)begin
	    if(state == ADDR)
			qspi_sio3_temp <= addr_lock[23 - (addr_cnt<<1)];
		else if(state == DATA)
			qspi_sio3_temp <= i_data[7 - (data_cnt<<1)]; 
		else
			qspi_sio3_temp <= qspi_sio3_temp ;
	end else
		qspi_sio3_temp <= qspi_sio3_temp ;
end

reg qspi_sio0_temp;//由于是三态门,需要定义中间变量
reg qspi_sio1_temp;
reg qspi_sio2_temp;
reg qspi_sio3_temp;
//在各状态下赋相对应的值,在写数据的时候i_wr信号为高,读时为低
assign qspi_sio0 = (state == CMD || state == ADDR) ? qspi_sio0_temp : (i_wr) ? qspi_sio0_temp : 1'bz;
assign qspi_sio1 = (state == ADDR) ? qspi_sio1_temp: (i_wr) ? qspi_sio1_temp: 1'bz;
assign qspi_sio2 = (state == ADDR) ? qspi_sio2_temp: (i_wr) ? qspi_sio2_temp: 1'bz;
assign qspi_sio3 = (state == ADDR) ? qspi_sio3_temp: (i_wr) ? qspi_sio3_temp: 1'bz;

always@(posedge clk)begin//QSPI发送数据,将数据线上的数据移位至r_data_temp寄存器
	if(rst)begin
		r_data_temp <= 8'b0;
	end else if(qspi_sck_p && (state == DATA))begin
			r_data_temp[7 - (data_cnt-1)<<1] <= qspi_sio3 ;
			r_data_temp[6 - (data_cnt-1)<<1] <= qspi_sio2 ;
			r_data_temp[5 - (data_cnt-1)<<1] <= qspi_sio1 ;
			r_data_temp[4 - (data_cnt-1)<<1] <= qspi_sio0 ;
	end else begin
		r_data_temp <= r_data_temp;
	end
end

always@(posedge clk)begin//将移位寄存器中的数据输出
	if(rst)
		o_data <= 8'b0;
	else if(data_cnt == 8'd4)
		o_data <= r_data_temp;
	else
		o_data  <= o_data;
end

endmodule

シミュレーション波形図

画像の説明を追加してください
画像の説明を追加してください
波形図では、コマンドワード0xEBがリード動作、0x38がライト動作、0x06がライトイネーブルコマンドを表しており、今回書き込まれるデータは0xF0に向かって0x01、0x12、0x23と順番に書き込まれていることが分かります。書き込んだデータと読み出したデータが一致していることを示し、QSPI 通信機能が正常であることを示します。実際のテストの後、この記事の QSPI レートは 75MHz に達する可能性があります。(FPGA クロック周波数は 150MHz)。

要約する

要約すると、基礎となる QSPI コードと SPI コードは同様の書き込み概念を持っていることがわかりますが、主な違いはコマンド ワードの書き込み、アドレスの書き込み、および 4 データ ラインのデータ収集です。最も重要なことは、特定の機能を実現するために実際のアプリケーションと組み合わせて記述することです。

おすすめ

転載: blog.csdn.net/m0_51575600/article/details/132693943