当前位置:   article > 正文

基于FPGA的QSPI底层驱动代码实现_qspi fpga verilog驱动

qspi fpga verilog驱动

QSPI简介

相信各位优秀的工程师们对SPI协议已经是非常了解了,SPI全名为串行外围设备接口(Serial Peripheral Interface),是一种高速全双工的同步通信总线,广泛应用于设备间的通讯传输。
而本文所要讲的QSPI,为SPI接口的扩展,Q代表quad即4倍传输的意思,也称为四线制SPI,因此该接口的传输速率将远远快于标准的SPI,其广泛应用于SPI Flash存储介质。下面本文将通过一个Flash芯片的Datasheet,来详细的描述该如何利用FPGA实现QSPI的通信。

写时序

QSPI写时序图
由时序图可以看到,图中总共有6个信号,从上至下分别为CE(片选信号)、CLK(时钟信号)、SIO0–SIO34根数据线。其中与SPI接口相似的是片选和时钟信号不变,在读写数据时片选信号均为低电平,在采样或发送数据时均在时钟的上升沿或者下降沿。唯一的区别在数据线由原来的MOSI、MISO变成了4根数据线,那么我们该如何将这四根数据线应用起来呢?由图中可知,SIO0会发送命令、地址以及数据,而SIO1–SIO3则只发送命令和数据,再进一步观察可以看到,写命令为0x38,它由8个CLK发送完成,地址信号总共24bit,由4跟数据线在6个CLK内同时完成发送,且每根数据线所发送的起始比特位都不同,最后便是发送数据,同理也由4根数据线同时进行发送,1个CLK发送4bit数据,发送的大小可由用户自己设定。总结一下,QSPI通信写的流程可以概括为先发送一个字节的命令字(这个命令字对于不同的芯片是不一样的),再是发送3个字节的地址(同理),最后才是发送数据。因此在FPGA的设计上就有思路了,最简单的方法就是采用状态机来描述这一过程,具体代码将在下面展示。

读时序

QSPI读时序图
由图可知,读时序的操作流程与写时序大同小异,只是命令字由0x38变成了0xEB,其余操作流程均与写时序相同,因此不再进行详细阐述。
但需要注意的是,由SPI扩展为QSPI,它已经不是全双工通信了,而是变成了半双工。SIO0–SIO3 4根线将变成三态门,也就是FPGA中的inout接口,需要满足特定的条件才能输入或者输出数据。
下面将给出QSPI通信的底层驱动代码,在实际工程应用中,还需要结合芯片的数据手册来编写应用层的程序,再结合底层的逻辑来实现特定的功能,例如利用QSPI或者SPI接口对某个Flash芯片进行读写。

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307

仿真波形图

请添加图片描述
请添加图片描述
波形图中命令字0xEB代表读操作,0x38代表写操作,0x06代表写使能命令,本次所写入的数据为0x01、0x12、0x23依次按照顺序至0xF0,由图中可以看出写入的数据与读出的数据是一致的,表明QSPI通讯功能正常。经过实测,本文的QSPI速率可达到75MHz。(FPGA时钟频率为150MHz)。

总结

综上,其实可以看出底层的QSPI代码与SPI代码的编写思路都是相似的,主要的区别就是写命令字、写地址以及4根数据线的数据采集。最关键的其实还是要结合实际的应用来进行编写,才能实现特定的功能!

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号