本章节讲述如何使用FPGA(纯verilog语言)实现NVME SSD硬盘的读写控制。
参考:
三、实战篇-NVME SSD控制之FPGA实现(一)-CSDN博客
二、实战篇-NVME SSD控制之ZYNQ实现(三)-CSDN博客

前面完成了FPGA和NVME SSD的PCIE CFG空间的配置。下面进行NVME SSD的寄存器配置,NVME SSD内部寄存器位于BAR空间中。

上一节通过S_AXI_CTL总线完成了PCIE配置空间的配置,本节通过控制S_AXI总线完成NVME SSD内部寄存器配置。

S_AXI_CTL总线采用AXI_FULL接口,AXI_FUL接口信号比较多,这里使用一个本地转AXI_FUL接口的模块(LOCAL_2_AXI)方便读写。

通过这个模块,这样我们通过本地接口就可以进行S_AXI总线接口的访问
cpp
module LOCAL_2_AXI #
(
parameter C_M_TARGET_SLAVE_BASE_ADDR = 32'h40000000,
parameter integer C_M_AXI_ID_WIDTH = 1,
parameter integer C_M_AXI_ADDR_WIDTH = 32,
parameter integer C_M_AXI_DATA_WIDTH = 128,
parameter integer C_M_AXI_AWUSER_WIDTH = 0,
parameter integer C_M_AXI_ARUSER_WIDTH = 0,
parameter integer C_M_AXI_WUSER_WIDTH = 0,
parameter integer C_M_AXI_RUSER_WIDTH = 0,
parameter integer C_M_AXI_BUSER_WIDTH = 0
)
(
// 本地读写接口
input wire local_wr_en,
input wire [C_M_AXI_ADDR_WIDTH-1:0]local_wr_addr,
input wire [C_M_AXI_DATA_WIDTH-1:0]local_wr_data,
input [C_M_AXI_DATA_WIDTH/8-1:0] i_WSTRB,
output reg local_wr_done,
input wire local_rd_en,
input wire [C_M_AXI_ADDR_WIDTH-1:0]local_rd_addr,
output reg [C_M_AXI_DATA_WIDTH-1:0]local_rd_data,
output reg local_rd_valid,
// AXI4 时钟复位
input wire M_AXI_ACLK,
input wire M_AXI_ARESETN,
// 写地址通道
output wire [C_M_AXI_ID_WIDTH-1 : 0] M_AXI_AWID,
output wire [C_M_AXI_ADDR_WIDTH-1 : 0] M_AXI_AWADDR,
output wire [7 : 0] M_AXI_AWLEN,
output wire [2 : 0] M_AXI_AWSIZE,
output wire [1 : 0] M_AXI_AWBURST,
output wire M_AXI_AWLOCK,
output wire [3 : 0] M_AXI_AWCACHE,
output wire [2 : 0] M_AXI_AWPROT,
output wire [3 : 0] M_AXI_AWQOS,
output wire [C_M_AXI_AWUSER_WIDTH-1 : 0] M_AXI_AWUSER,
output wire M_AXI_AWVALID,
input wire M_AXI_AWREADY,
// 写数据通道
output wire [C_M_AXI_DATA_WIDTH-1 : 0] M_AXI_WDATA,
output wire [C_M_AXI_DATA_WIDTH/8-1 : 0] M_AXI_WSTRB,
output wire M_AXI_WLAST,
output wire [C_M_AXI_WUSER_WIDTH-1 : 0] M_AXI_WUSER,
output wire M_AXI_WVALID,
input wire M_AXI_WREADY,
// 写响应通道
input wire [C_M_AXI_ID_WIDTH-1 : 0] M_AXI_BID,
input wire [1 : 0] M_AXI_BRESP,
input wire [C_M_AXI_BUSER_WIDTH-1 : 0] M_AXI_BUSER,
input wire M_AXI_BVALID,
output wire M_AXI_BREADY,
// 读地址通道
output wire [C_M_AXI_ID_WIDTH-1 : 0] M_AXI_ARID,
output wire [C_M_AXI_ADDR_WIDTH-1 : 0] M_AXI_ARADDR,
output wire [7 : 0] M_AXI_ARLEN,
output wire [2 : 0] M_AXI_ARSIZE,
output wire [1 : 0] M_AXI_ARBURST,
output wire M_AXI_ARLOCK,
output wire [3 : 0] M_AXI_ARCACHE,
output wire [2 : 0] M_AXI_ARPROT,
output wire [3 : 0] M_AXI_ARQOS,
output wire [C_M_AXI_ARUSER_WIDTH-1 : 0] M_AXI_ARUSER,
output wire M_AXI_ARVALID,
input wire M_AXI_ARREADY,
// 读数据通道
input wire [C_M_AXI_ID_WIDTH-1 : 0] M_AXI_RID,
input wire [C_M_AXI_DATA_WIDTH-1 : 0] M_AXI_RDATA,
input wire [1 : 0] M_AXI_RRESP,
input wire M_AXI_RLAST,
input wire [C_M_AXI_RUSER_WIDTH-1 : 0] M_AXI_RUSER,
input wire M_AXI_RVALID,
output wire M_AXI_RREADY
);
将LOCAL_2_AXI模块的M_AXI连接到S_AXI总线,本地信号引出到外部进行控制。

新建一个nvme_reg_cfg模块用于控制LOCAL_2_AXI模块的本地接口,进行NVME SSD寄存器读写
cpp
module nvme_reg_cfg#(
parameter integer C_M_AXI_ADDR_WIDTH = 32,
parameter integer C_M_AXI_DATA_WIDTH = 128,
parameter NVME_ADMIN_QSIZE =16'd16,
parameter ADMIN_SQ_BASE =32'h1010_0000,
parameter ADMIN_CQ_BASE =32'h1010_1000
)(
input wire i_aclk,
input wire i_aresetn,
input i_cfg_start,
output o_cfg_done,
input i_wr_door_req,
input [2:0] i_wr_door_type,
input [31:0] i_wr_door_data,
output o_wr_door_done,
// 本地写接口
output o_wr_en, // 写使能,高有效
output [C_M_AXI_ADDR_WIDTH-1:0] o_wr_addr, // 本地写地址(偏移地址)
output [C_M_AXI_DATA_WIDTH-1:0] o_wr_data, // 本地待写入数据
output [C_M_AXI_DATA_WIDTH/8-1:0] o_WSTRB,
input i_wr_done,
// 读接口
output o_rd_en, // 读使能,高有效
output [C_M_AXI_ADDR_WIDTH-1:0] o_rd_addr, // 本地读地址(偏移地址)
input [C_M_AXI_DATA_WIDTH-1:0] i_rd_data, // 读回数据
input i_rd_valid // 读数据有效标志
);
寄存器偏移地址都是固定的,可以将其定义为parameter
cpp
parameter RegOffset_CAP_L =32'h0000_0000;
parameter RegOffset_CAP_H =32'h0000_0004;
parameter RegOffset_VS =32'h0000_0008;
parameter RegOffset_CC =32'h0000_0014;
parameter RegOffset_CSTS =32'h0000_001C;
parameter RegOffset_AQA =32'h0000_0024;
parameter RegOffset_ASQ_L =32'h0000_0028;
parameter RegOffset_ASQ_H =32'h0000_002C;
parameter RegOffset_ACQ_L =32'h0000_0030;
parameter RegOffset_ACQ_H =32'h0000_0034;
parameter RegOffset_Admin_SQTDBL =32'h0000_1000;
parameter RegOffset_Admin_CQHDBL =32'h0000_1004;
parameter RegOffset_IO_SQ0TDBL =32'h0000_1008;
parameter RegOffset_IO_CQ0HDBL =32'h0000_100C;
parameter RegOffset_IO_SQ1TDBL =32'h0000_1010;
parameter RegOffset_IO_CQ1HDBL =32'h0000_1014;
主要状态流程如下
cpp
// cstate
always@(posedge i_aclk or negedge i_aresetn)begin
if(!i_aresetn)begin
cstate<=0;
end else begin
cstate<=nstate;
end
end
always@(*)begin
case(cstate)
0:begin if(start_pdg) nstate=1;
else nstate=0;end
1:begin if(rd_cnt>=4) nstate=2;//RD RC ID
else nstate=1;end
2:begin if(i_rd_valid) nstate=3;//rd ready
else nstate=2;end
3:begin if(rd_vaild && rdata[0]==0) nstate=4;//
else nstate=2;end
4:begin if(i_wr_done) nstate=5;//wr cc
else nstate=4;end
5:begin if(i_wr_done) nstate=6;//wr AQA
else nstate=5;end
6:begin if(i_wr_done) nstate=7;//wr ASQ_L
else nstate=6;end
7:begin if(i_wr_done) nstate=8;//wr ASQ_H
else nstate=7;end
8:begin if(i_wr_done) nstate=9;//wr ACQ_L
else nstate=8;end
9:begin if(i_wr_done) nstate=10;//wr ACQ_H
else nstate=9;end
10:begin if(i_wr_done) nstate=11;//wr cc
else nstate=10;end
11:begin if(i_rd_valid) nstate=12;//rd ready
else nstate=11;end
12:begin if(rd_vaild && rdata[0]==1) nstate=13;//
else nstate=11;end
//------
13:begin if(wr_door_req) nstate=14;
else nstate=13;end
14:begin if(i_wr_done) nstate=15;
else nstate=14;end
15:begin nstate=13;end
endcase
end
读寄存器
cpp
//rd
always@(posedge i_aclk or negedge i_aresetn)begin
if(!i_aresetn)begin
rd_en<=0;
rd_addr<=0;
end else begin
if(cstate==1)begin
if(rd_cnt==0)begin
rd_addr<=RegOffset_CAP_L;
if(i_rd_valid)
rd_en<=0;
else
rd_en<=1;
end else if(rd_cnt==1)begin
rd_addr<=RegOffset_CAP_H;
if(i_rd_valid)
rd_en<=0;
else
rd_en<=1;
end else if(rd_cnt==2)begin
rd_addr<=RegOffset_VS;
if(i_rd_valid)
rd_en<=0;
else
rd_en<=1;
end else if(rd_cnt==3)begin
rd_addr<=RegOffset_CC;
if(i_rd_valid)
rd_en<=0;
else
rd_en<=1;
end else
rd_en<=0;
end else if(cstate==2||cstate==11)begin
rd_addr<=RegOffset_CSTS;
if(i_rd_valid)
rd_en<=0;
else
rd_en<=1;
end else begin
rd_en<=0;
end
end
end
写寄存器
cpp
//wr
always@(posedge i_aclk or negedge i_aresetn)begin
if(!i_aresetn)begin
wr_en<=0;
wr_addr<=0;
wr_data<=0;
end else begin
if(cstate==4)begin
wr_addr<=RegOffset_CC;
wr_data<=32'h0046_0000;
if(i_wr_done)
wr_en<=0;
else
wr_en<=1;
end else if(cstate==5)begin
wr_addr<=RegOffset_AQA;
wr_data<={NVME_ADMIN_QSIZE,NVME_ADMIN_QSIZE};
if(i_wr_done)
wr_en<=0;
else
wr_en<=1;
end else if(cstate==6)begin
wr_addr<=RegOffset_ASQ_L;
wr_data<=ADMIN_SQ_BASE;
if(i_wr_done)
wr_en<=0;
else
wr_en<=1;
end else if(cstate==7)begin
wr_addr<=RegOffset_ASQ_H;
wr_data<=32'h0000_0000;
if(i_wr_done)
wr_en<=0;
else
wr_en<=1;
end else if(cstate==8)begin
wr_addr<=RegOffset_ACQ_L;
wr_data<=ADMIN_CQ_BASE;
if(i_wr_done)
wr_en<=0;
else
wr_en<=1;
end else if(cstate==9)begin
wr_addr<=RegOffset_ACQ_H;
wr_data<=32'h0000_0000;
if(i_wr_done)
wr_en<=0;
else
wr_en<=1;
end else if(cstate==10)begin
wr_addr<=RegOffset_CC;
wr_data<=32'h0046_0001;
if(i_wr_done)
wr_en<=0;
else
wr_en<=1;
end else if(cstate==14)begin
wr_data<=i_wr_door_data;
case(i_wr_door_type)
0:wr_addr<=RegOffset_Admin_SQTDBL;
1:wr_addr<=RegOffset_Admin_CQHDBL;
2:wr_addr<=RegOffset_IO_SQ0TDBL;
3:wr_addr<=RegOffset_IO_CQ0HDBL;
4:wr_addr<=RegOffset_IO_SQ1TDBL;
5:wr_addr<=RegOffset_IO_CQ1HDBL;
default:wr_addr<=RegOffset_Admin_SQTDBL;
endcase
if(i_wr_done)begin
wr_en<=0;
end else begin
wr_en<=1;
end
end else begin
wr_en<=0;
end
end
end
主要寄存器配置流程和ZYNQ控制NVME SSD配置一致。