FPGA 기반의 QSPI 기본 드라이버 코드 구현
QSPI 소개
뛰어난 엔지니어들은 모두 이미 SPI 프로토콜에 대해 매우 잘 알고 있다고 생각합니다. SPI의 전체 이름은 Serial Peripheral Interface입니다. 장치 간 통신 전송에 널리 사용되는 고속, 전이중 동기 통신 버스입니다. .
이번 글에서 다루게 될 QSPI는 SPI 인터페이스의 확장으로, Q는 쿼드(quad)의 약자로 전송 속도가 4배라는 의미로 4와이어 SPI라고도 불린다.따라서 이 인터페이스의 전송 속도는 다음과 같다. 표준 SPI보다 훨씬 빠르며 SPI 플래시 저장 매체에 널리 사용됩니다. 다음 기사에서는 플래시 칩 데이터시트를 사용하여 FPGA를 사용하여 QSPI 통신을 구현하는 방법을 자세히 설명합니다.
쓰기 타이밍
타이밍 다이어그램에서 볼 수 있듯이 다이어그램에는 위에서 아래로 CE(칩 선택 신호), CLK(클럭 신호), SIO0~SIO34 데이터 라인 등 총 6개의 신호가 있습니다. SPI 인터페이스와 유사하게 칩 선택 및 클록 신호는 변경되지 않습니다.데이터를 읽고 쓸 때 칩 선택 신호는 모두 로우 레벨이고 데이터를 샘플링하거나 보낼 때 클록의 상승 에지 또는 하강 에지에 있습니다. 유일한 차이점은 데이터 라인이 원래 MOSI 및 MISO에서 4개의 데이터 라인으로 변경되었다는 것인데, 이 4개의 데이터 라인을 어떻게 적용합니까? 그림에서 볼 수 있듯이 SIO0은 명령, 주소 및 데이터를 보내는 반면 SIO1~SIO3은 명령과 데이터만 보냅니다. 자세히 관찰하면 쓰기 명령은 0x38이고 8개의 CLK에서 전송되며 주소 신호 합계는 다음과 같습니다. 24비트. 6개의 CLK 내에서 4개의 데이터 라인에 의해 동시에 전송이 완료되며, 각 데이터 라인이 보내는 시작 비트가 서로 다르며, 최종적으로 데이터가 전송된다. 마찬가지로 4개의 데이터 라인이 동시에 전송되고, 1 CLK 4비트 데이터를 전송합니다. 전송되는 크기는 사용자가 설정할 수 있습니다. 요약하자면, QSPI 통신 쓰기 프로세스는 먼저 1바이트 명령 단어를 전송하고(이 명령 단어는 칩마다 다름) 3바이트 주소를 전송하고(동일 원리) 마지막으로 데이터를 전송하는 것으로 요약할 수 있습니다. 따라서 FPGA 설계에는 아이디어가 있으며 가장 간단한 방법은 상태 머신을 사용하여 이 프로세스를 설명하는 것입니다. 구체적인 코드는 아래에 나와 있습니다.
읽기 타이밍
그림에서 알 수 있듯이, 읽기 타이밍의 동작 흐름은 명령 워드가 0x38에서 0xEB로 변경된 것을 제외하면 쓰기 타이밍과 유사하며, 나머지 동작 흐름은 쓰기 타이밍과 동일하며, 그래서 자세히 설명하지 않겠습니다.
그러나 SPI가 QSPI로 확장되면 더 이상 전이중 통신이 아니라 반이중 통신이 된다는 점에 유의해야 합니다. 4개의 라인 SIO0~SIO3은 FPGA의 입력 인터페이스인 3상태 게이트가 되며, 데이터를 입력하거나 출력하려면 특정 조건을 충족해야 합니다.
다음은 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개 데이터 라인의 데이터 수집입니다. 가장 중요한 것은 특정 기능을 구현하기 위해 실제 애플리케이션과 연계하여 작성하는 것입니다!